                                
                            ...................
		          ...::: phearless zine #2 :::...
		      
................>---[ Phearless Challenge #2: Reversme ]---<..............

...........................>---[ by sunnis ]---<..........................
                                                   sunnis[at]gmail[dot]com

Sadrzaj:

       <1>  Uvod u sekciju - Phearless Challenge
       
       <2>  Uvod u reversme
       
       <3>  Runtime nativna dekompresija i nativni API
       
       <4>  Phun with CLR
       
       <5>  Tko kaze da nema asemblera u .NET-u?
       
       <6>  asm i Nt*
       
       <7>  Brainfuck
       
       <8>  Running-line
       
       <9>  One ring to bring 'em all...
       
       <10> Greets
       
       <11> Reference


///////////////////////////////////////////////////////////////////////////
--[ Uvod u sekciju - Phearless Challenge
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

Ovaj papir jest prvi u sekciji Phearless Challenge. Nosi broj 2 iako je de facto 
prvi papir u toj kategoriji, samo zato da se poklopi sa numerickom verzijom 
samog zine-a, kako ne bi doslo do zabuna.

Phearless Challenge jest nova kategorija koja je namijenjena objavljivanju
izazova samim citateljima (i piscima :) zine-a. U njoj ce biti objavljeni
prije svega *originalni* izazovi koji bi citatelji trebali rijesiti, te rjesenje
poslati autoru izazova, kako bi stekli besmrtnu slavu u sluzbenom rjesenju koje
ce sam autor objaviti u zine-u ;)

A izazov moze biti manje-vise sve. Najscesce ce to biti neki crackme, reversme, 
exploitme, neko elegantno rjesenje nekog hacking problema...uglavnom bilo sto 
sto autor odgovarajuceg izazova smisli. Naglasak je, ponavljam, na 
*originalnosti*. c/p-eri i oni koji su prvi put u zivotu otkrili i napisali neki 
komadic koda, i sad zele ostati "zapamceni" u povijesti kao phearless autori 
mogu komotno odjebati. Ova sekcija nije namijenjena njima niti ce ikad biti.

A autor izazova moze biti bilo tko, tj. ne ogranicavamo se samo na standardne 
autore clanaka :) Dakle ako ste bas *VI* dovoljno l33t, i imate neki jako kul 
problemcic i odgovarajuce rjesenje na pameti, bilo sto sto mislite da je 
dovoljno zanimljivo da udje u ovu sekciju, pozivam Vas, nemojte se libiti 
poslati problem meni, ili uredniku zine-a, pa ako je dovoljno dobar i zanimljiv,
slobodno cemo ga objaviti!

Ali ponavljam, *samo* problem, bez rjesenja (kojeg cete bas Vi prezentirati u 
broju zine-a nakon objavljivanja problema skupa sa pristiglim rjesenjima). U
stvari najbolje je da dodjete na #ugs @ irc.krstarica.com pa da o tome popricamo 
:)

Konkretno sam ja imao na umu da se izazov objavljuje ili na sluzbenoj stranici
od zine-a, ili na prikladnom forumu na [es]. Recimo ovaj moj je objavljen ovdje:

http://www.elitesecurity.org/poruka/692956

Buduci da se konkretno radi o reverseme-u, ide u Cracking forum. Recimo bas ova
sekcija zine-a cilja tematski konkretno na skupinu foruma u kategoriji 
Sigurnost:

http://www.elitesecurity.org/kategorija/9

Ali naravno, prihvacamo sve sto je dovoljno originalno i zanimljivo :)


///////////////////////////////////////////////////////////////////////////
--[ Uvod u reversme
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\


Ovaj reversme sam napisao cisto iz zabave, da dokazem prije svega sebi da su 
neke stvari moguce, i da uvjerim neke craxore koji vele "Tko kaze da je VB 
lame", te se ponasaju bahato i umisljeno i misle da su popili svu pamet svijeta.

E pa niste gospodo. Konkretno za ovaj izazov u 10 dana koliko tamo stoji nisam
dobio nijedan mejl u kojem netko kaze da ga je i pokusao rijesiti, a kamoli da
me ispita o nekim nacinima zastite :)

E pa da sad nesto malo vise kazem o samom reversmeju.

Ovo je prije svega zamisljen kao fascinantan komadic koda koji ce kombinirati 
neko slojeva (razina) zastite u razlicitim programskim jezicima i u razlicitim
okolinima. Konkretno su demonstrirane uporabe sljedecih tehnologija:

- neki popularni i manje popularni anti-* trikovi
- nativna runtime dekompresija
- inline koristenje nativnih API-ja u asmu i u C/C++
- zastita MSIL koda
- embeddanje asm koda u .NET jezike
- running line enkripcija
- skakanje u ring0 i pozivanje kernel-mode servisa bez pisanja drivera
- brainfuck ;)

Uglavnom, prije nego sto sam ga i poceo pisat, manje-vise sam cijeli crackme kao
takav imao vec "u glavi" kako bi trebao izgledat, te sam ga pisao (kako to i 
prilici :) od tezih prema laksim dijelovima. Dosta stvari ovdje prikazani su 
jako kul, i premda recimo neki anti-* trikovi nisu novi, sa vecinom ostalih 
tehnologija ovdje prezenziranih jest 90% crackera jako slabo upoznato. Tj. 
nemaju pojma.

Cilj je bio prije svega da svi nesto nauce. Onaj koji cita papir, onaj koji
rijesava izazov i onaj koji ga pise. Ja sam svoj dio posla napravio, a nadam
se da cete citajuci ovaj tekst uzivati barem onoliko koliko sam ja BSOD-ova
dobio dok sam radio ring0 komponentu ;)


///////////////////////////////////////////////////////////////////////////
--[ Runtime nativna dekompresija i nativni API
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

Prvi sloj zastite jest mala aplikacija napisana u manje-vise cistom C-u, koja 
koristi 3 nedokumentirane nativne fje: RtlCompressBuffer(),
RtlDecompressBuffer() i RtlGetCompressionWorkSpaceSize() (vise informacija o 
ovim fjama mozete pronaci na [1] [2] i [3]). Analizirajmo kako one izgledaju 
redom, koristeci njihove deklaracije iz \private\ntos\rtl\ntrtlp.h: 

NTSTATUS
RtlCompressBuffer (
    IN USHORT        CompressionEngine,
    IN PUCHAR        UncompressedBuffer,
    IN ULONG         UncompressedBufferSize,
    OUT PUCHAR       CompressedBuffer,
    IN ULONG         CompressedBufferSize,
    IN ULONG         UncompressedChunkSize,
    OUT PULONG       FinalCompressedSize,
    IN PVOID         WorkSpace
    );
   
Sa NtAllocateVirtualMemory() alociramo buffer koji ce koristiti 
RtlCompressBuffer(). Zasad NT podrzava samo jedan kompresijski format koji 
odgovara CompressionEngine parametru, a koji se zove LZNT1 (vjerojatno bazirano 
na Lempel-Ziv enkodiranju) te je definiran izmedju ostaloga sljedecom konstantom 
u winnt.h:

#define COMPRESSION_FORMAT_NONE          (0x0000)   
#define COMPRESSION_FORMAT_DEFAULT       (0x0001)   
#define COMPRESSION_FORMAT_LZNT1         (0x0002) 
  
#define COMPRESSION_ENGINE_STANDARD      (0x0000)   
#define COMPRESSION_ENGINE_MAXIMUM       (0x0100)

Sto se tice ostalih kompresijskih formata, disasemblirajmo 
RtlCompressBuffer!ntdll.dll sa ucitanim debug simbolima te sredimo malo imena 
parametara i labela, te pogledajmo sto se sve tamo krije:

.text:7C9612AD ; Exported entry 440. RtlCompressBuffer
.text:7C9612AD                 mov     edi, edi
.text:7C9612AF
.text:7C9612AF ; --------------- S U B R O U T I N E ---------------------------
.text:7C9612AF
.text:7C9612AF ; Attributes: bp-based frame
.text:7C9612AF
.text:7C9612AF sub_7C9612AF    proc near
.text:7C9612AF
.text:7C9612AF CompressionEngine= dword ptr  8
.text:7C9612AF UncompressedBuffer= dword ptr  0Ch
.text:7C9612AF UncompressedBufferSize= dword ptr  10h
.text:7C9612AF CompressedBuffer= dword ptr  14h
.text:7C9612AF CompressedBufferSize= dword ptr  18h
.text:7C9612AF UncompressedChunkSize= dword ptr  1Ch
.text:7C9612AF FinalCompressedSize= dword ptr  20h
.text:7C9612AF WorkSpace       = dword ptr  24h
.text:7C9612AF
.text:7C9612AF                 push    ebp
.text:7C9612B0                 mov     ebp, esp
.text:7C9612B2                 mov     ecx, [ebp+CompressionEngine]
.text:7C9612B5                 xor     eax, eax
.text:7C9612B7                 mov     al, cl

                               ; maskiraj LSB za COMPRESSION_FORMAT_* konstantu


.text:7C9612B9                 and     eax, 0FFh               
.text:7C9612BE                 jz      short __invalid_param
.text:7C9612C0                 cmp     ax, 1

                               ; nevrijedeci parametri su 0 i 1
                               
.text:7C9612C4                 jz      short __invalid_param   
.text:7C9612C6                 test    al, 0F0h
.text:7C9612C8                 jz      short __ok_compression
.text:7C9612CA
.text:7C9612CA loc_7C9612CA:

                               ; nepodrzane kompresije su 3h - 0Fh

.text:7C9612CA                 mov     eax, STATUS_UNSUPPORTED_COMPRESSION   
.text:7C9612CF                 jmp     short __unsupported_compression
.text:7C9612D1
.text:7C9612D1 __ok_compression:    
.text:7C9612D1                 push    [ebp+WorkSpace]

                       ; outmaskaj LSB i ostale parametre proslijedi standardno

.text:7C9612D4                 and     ecx, not 0FFh           
.text:7C9612DA                 push    [ebp+FinalCompressedSize]
.text:7C9612DD                 movzx   eax, ax
.text:7C9612E0                 push    [ebp+UncompressedChunkSize]
.text:7C9612E3                 push    [ebp+CompressedBufferSize]
.text:7C9612E6                 push    [ebp+CompressedBuffer]
.text:7C9612E9                 push    [ebp+UncompressedBufferSize]
.text:7C9612EC                 push    [ebp+UncompressedBuffer]
.text:7C9612EF                 push    ecx
.text:7C9612F0                 call    ds:_RtlCompressBufferProcs[eax*4]
.text:7C9612F7                 jmp     short __unsupported_compression
.text:7C9612F9
.text:7C9612F9 __invalid_param:                        
.text:7C9612F9                 mov     eax, STATUS_INVALID_PARAMETER
.text:7C9612FE
.text:7C9612FE __unsupported_compression:
.text:7C9612FE                 pop     ebp        ; vrati frame pointer i izadji
.text:7C9612FF                 retn    20h
.text:7C9612FF sub_7C9612AF    endp
.text:7C9612FF
.text:7C961302                 align 4
.text:7C961304 _RtlCompressBufferProcs dd 0
.text:7C961308                 dd 0
.text:7C96130C                 dd offset _RtlCompressBufferLZNT1@32 ; RtlCompressBufferLZNT1(x,x,x,x,x,x,x,x)
.text:7C961310                 dd offset _RtlCompressBufferNS@32 ; RtlCompressBufferNS(x,x,x,x,x,x,x,x)
.text:7C961314                 dd offset _RtlCompressBufferNS@32 ; RtlCompressBufferNS(x,x,x,x,x,x,x,x)
.text:7C961318                 dd offset _RtlCompressBufferNS@32 ; RtlCompressBufferNS(x,x,x,x,x,x,x,x)
.text:7C96131C                 dd offset _RtlCompressBufferNS@32 ; RtlCompressBufferNS(x,x,x,x,x,x,x,x)
.text:7C961320                 dd offset _RtlCompressBufferNS@32 ; RtlCompressBufferNS(x,x,x,x,x,x,x,x)
.text:7C961324                 dd 90909090h
.text:7C961328                 db 90h


RtlCompressBufferProcs niz koji se nalazi na adresi 7C961304h predstavlja 
tablicu adresa fja koje sluze za kompresiju odgovarajucim engine-ima. Na adresi 
7C9612F0h se nalazi call na odgovarajuci indeks u tu tablicu, u ovisnosti o 
vrijednosti u eax, u kojem ce se u konacnici nalaziti LSB CompressionEngine 
parametra.

E sad, u toj se tablici nalazi 6 vrijedecih adresa, od cega su prakticno 2 
razlicite fje. Na ideksima 0 i 1 se nalaze NULL ptri, ali oni se filtriraju na 
adresama 7C9612BEh i 7C9612C4h. Indeks 2 koji dogovara COMPRESSION_FORMAT_LZNT1 
parametru pokazuje na internu RtlCompressBufferLZNT1() fju koja nije exportana, 
dok ostali pokazuju na fju RtlCompressBufferNS() na kojoj se nalazi pogodite 
sto?

.text:7C961416 ; --------------- S U B R O U T I N E ---------------------------
.text:7C961416
.text:7C961416
.text:7C961416 ; __stdcall RtlCompressBufferNS(x,x,x,x,x,x,x,x)
.text:7C961416 _RtlCompressBufferNS@32 proc near 
.text:7C961416                 mov     eax, STATUS_UNSUPPORTED_COMPRESSION
.text:7C96141B                 retn    20h
.text:7C96141B _RtlCompressBufferNS@32 endp

Dakle onaj NS postfix jest vjerojatno kratica od Not Supported :>

Kao sto rekoh, RtlCompressBufferLZNT1() nije nazalost exportana pa cemo trebati 
joj pristupati preko RtlCompressBuffer(). Sto se tice ostalih parametara, oni su 
manje vise sami po sebi razumljivi po svome nazivu, jedino se mozda isplati 
objasniti jos ove:

IN ULONG         UncompressedChunkSize

 - obicno stavi 1000h, tj. jedan page, a mozes i 0 komotno...

IN PVOID         WorkSpace                

 - ptr na privremeni buffer nad kojim kompresijski engine obavlja svoj posao. 
Njega sami alociramo (recimo koristeci NtAllocateVirtualMemory()), a njegovu 
velicinu saznajemo dinamicki koristeci RtlGetCompressionWorkSpaceSize().

NTSTATUS
RtlGetCompressionWorkSpaceSize (
    IN USHORT CompressionEngine,
    OUT PULONG CompressBufferWorkSpaceSize,              
    OUT PULONG CompressFragmentWorkSpaceSize
    );

pri cemu:

OUT PULONG CompressBufferWorkSpaceSize

 - vraca potrebnu velicinu buffera kojeg trebamo alocirati
 
OUT PULONG CompressFragmentWorkSpaceSize

 - obicno vraca 4000h

Ovaj API vjerojatno postoji u slucaju da u buducnosti budu implementirani neki 
drugi algoritmi i kodiranja osim LZNT1. Osim tog jednog algoritma (algoritam 
cine bitovi 0-15 u CompressionEngine parametru), postoje i 2 engine-a koje ovaj 
algoritam podrzava, jedan za standardnu i jedan za visoku kompresiju 
(COMPRESSION_ENGINE_STANDARD i COMPRESSION_ENGINE_MAXIMUM). Ovaj za visoku 
kompresiju je i do 5 puta sporiji, ali zato kompresira dodatno 5-15% bolje. U 
konacnici je CompressionEngine bitwise OR odabranog algoritma i razine 
kompresije. 

#define COMPRESSION_ENGINE_STANDARD      (0x0000)   
#define COMPRESSION_ENGINE_MAXIMUM       (0x0100)

Specificno da demonstriram uporabu ovih fja, napisao sam jedan mali C++ (dobro, 
manje-vise je to cisti C :) proggy koji kompresira/dekompresira proizvoljnu 
datoteku. Mozete ga kompajlirati koristeci Visual C++ 2005 Express.

------------------------------ C O D E ------------------------------------

#include "stdafx.h"              // precompiled headeri koje generira AppWizard
#include <iostream>
#include <windows.h>
using namespace std;

//
// Neke korisne strukture i tipovi koji ce nam trebati.
//

#define COMPRESSION_FORMAT_NONE          (0x0000)   
#define COMPRESSION_FORMAT_DEFAULT       (0x0001)   
#define COMPRESSION_FORMAT_LZNT1         (0x0002)       // samo ovaj koristimo!!
  
#define COMPRESSION_ENGINE_STANDARD      (0x0000)   
#define COMPRESSION_ENGINE_MAXIMUM       (0x0100)

typedef UINT NTSTATUS;       // nativni API-ji vracaju NTSTATUS kod u eax

#define STATUS_SUCCESS                   ((NTSTATUS)0x00000000L)

typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PWSTR  Buffer;
} UNICODE_STRING;
typedef UNICODE_STRING *PUNICODE_STRING;

typedef struct _OBJECT_ATTRIBUTES {
    ULONG Length;
    HANDLE RootDirectory;
    PUNICODE_STRING ObjectName;
    ULONG Attributes;
    PVOID SecurityDescriptor;
    PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES;
typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES;

typedef enum _SECTION_INHERIT {
    ViewShare = 1,
    ViewUnmap = 2
} SECTION_INHERIT;

//
// Deklaracije nativnih fja koje importamo iz ntdll.dll
//

NTSTATUS ( __stdcall *NtCreateSection ) ( 
    OUT PHANDLE                    SectionHandle,
    IN ACCESS_MASK                 DesiredAccess,
    IN POBJECT_ATTRIBUTES          ObjectAttributes                   OPTIONAL,
    IN PLARGE_INTEGER              MaximumSize                        OPTIONAL,
    IN ULONG                       SectionPageProtection,
    IN ULONG                       AllocationAttributes,
    IN HANDLE                      FileHandle                         OPTIONAL
     );

NTSTATUS ( __stdcall *NtMapViewOfSection ) (
    IN HANDLE                      SectionHandle,
    IN HANDLE                      ProcessHandle,
    IN OUT PVOID                   *BaseAddress,
    IN ULONG_PTR                   ZeroBits,
    IN SIZE_T                      CommitSize,
    IN OUT PLARGE_INTEGER          SectionOffset                      OPTIONAL,
    IN OUT PSIZE_T                 ViewSize,
    IN SECTION_INHERIT             InheritDisposition,
    IN ULONG                       AllocationType,
    IN ULONG                       Protect
    );

NTSTATUS ( __stdcall *NtUnmapViewOfSection ) (
    IN HANDLE                      ProcessHandle,
    IN PVOID                       BaseAddress
     );

NTSTATUS ( __stdcall *NtClose ) (
    IN HANDLE                      Handle
    );

NTSTATUS ( __stdcall *RtlGetCompressionWorkSpaceSize ) (
    IN USHORT                      CompressionEngine,
    OUT PULONG                     CompressBufferWorkSpaceSize,
    OUT PULONG                     CompressFragmentWorkSpaceSize
    );

NTSTATUS ( __stdcall *RtlCompressBuffer ) (
    IN USHORT                      CompressionEngine,
    IN PUCHAR                      UncompressedBuffer,
    IN ULONG                       UncompressedBufferSize,
    OUT PUCHAR                     CompressedBuffer,
    IN ULONG                       CompressedBufferSize,
    IN ULONG                       UncompressedChunkSize,
    OUT PULONG                     FinalCompressedSize,
    IN PVOID                       WorkSpace
    );

NTSTATUS ( __stdcall *RtlDecompressBuffer ) (
       IN USHORT                   CompressionEngine,
    OUT PUCHAR                     UncompressedBuffer,
    IN ULONG                       UncompressedBufferSize,
    IN PUCHAR                      CompressedBuffer,
    IN ULONG                       CompressedBufferSize,
    OUT PULONG                     FinalUncompressedSize
    );

NTSTATUS ( __stdcall *NtAllocateVirtualMemory ) (
    IN HANDLE                      ProcessHandle,
    IN OUT PVOID                   *BaseAddress,
    IN ULONG_PTR                   ZeroBits,
    IN OUT PSIZE_T                 RegionSize,
    IN ULONG                       AllocationType,
    IN ULONG                       Protect
     );


NTSTATUS ( _stdcall *NtFreeVirtualMemory ) (
    IN HANDLE                      ProcessHandle,
    IN OUT PVOID                   *BaseAddress,
    IN OUT PSIZE_T                 RegionSize,
    IN ULONG                       FreeType
     );

//
// Ispisuje banner o pravilnoj uporabi toola u slucaju pogresnih parametara.
//

void Uporaba( LPSTR ime )
{
       cout << "Tool za (de)kompresiju datoteka koristeci nativni API"
               "(x) 2005 sunnis" << endl;
       cout << ime << " [opcije] <ime datoteke>" << endl;
       cout << "Opcije:" << endl;
       cout << "  -x:\t Kompresiraj" << endl;
       cout << "  -d:\t Dekompresiraj" << endl;
}

//
// Trazi adrese fja u ntdll.dll, te vraca FALSE ukoliko se bar jedna od njih 
// nije mogla naci.
//

BOOLEAN LocirajNTDLLFje()                 
{
       if ( !(NtCreateSection = (NTSTATUS (__stdcall *)
                     ( PHANDLE,
                       ACCESS_MASK,POBJECT_ATTRIBUTES,
                       PLARGE_INTEGER,
                       ULONG,
                       ULONG,
                       HANDLE ))
              GetProcAddress( GetModuleHandle("ntdll.dll"), 
                              "NtCreateSection" )) ) {
              return ( FALSE );
       }

       else if ( !(NtMapViewOfSection = (NTSTATUS (__stdcall *)
                     ( HANDLE,
                       HANDLE, 
                       PVOID *, 
                       ULONG_PTR, 
                       SIZE_T, 
                       PLARGE_INTEGER, 
                       PSIZE_T, 
                       SECTION_INHERIT, 
                       ULONG,
                       ULONG ))
              GetProcAddress( GetModuleHandle("ntdll.dll"), 
                              "NtMapViewOfSection" )) ) {
              return ( FALSE );
       }

       else if ( !(NtUnmapViewOfSection = (NTSTATUS (__stdcall *)
                     ( HANDLE, 
                       PVOID ))
              GetProcAddress( GetModuleHandle("ntdll.dll"), 
                              "NtUnmapViewOfSection" )) ) {
              return ( FALSE );
       }

       else if ( !(NtClose = (NTSTATUS (__stdcall *)(HANDLE))
                     GetProcAddress( GetModuleHandle("ntdll.dll"), 
                     "NtClose" )) ) {
              return ( FALSE );
       }

       else if ( !(RtlGetCompressionWorkSpaceSize = (NTSTATUS (__stdcall *)
                     ( USHORT, 
                       PULONG, 
                       PULONG ))
                     GetProcAddress( GetModuleHandle("ntdll.dll"), 
                                     "RtlGetCompressionWorkSpaceSize" )) ) {
              return ( FALSE );
       }

       else if ( !(RtlCompressBuffer = (NTSTATUS (__stdcall *)
                     ( USHORT, 
                       PUCHAR, 
                       ULONG, 
                       PUCHAR, 
                       ULONG, 
                       ULONG, 
                       PULONG, 
                       PVOID ))
              GetProcAddress( GetModuleHandle("ntdll.dll"), 
                                     "RtlCompressBuffer" )) ) {
              return ( FALSE );
       }

       else if ( !(RtlDecompressBuffer = (NTSTATUS (__stdcall *)
                     ( USHORT, 
                       UCHAR, 
                       ULONG, 
                       PUCHAR, 
                       ULONG, 
                       PULONG ))
              GetProcAddress( GetModuleHandle("ntdll.dll"), 
                              "RtlDecompressBuffer" )) ) {
              return ( FALSE );
       }

       else if ( !(NtAllocateVirtualMemory = (NTSTATUS (__stdcall *)
                     ( HANDLE, 
                      PVOID *, 
                      ULONG_PTR, 
                      PSIZE_T, 
                      ULONG, 
                      ULONG ))
                     GetProcAddress( GetModuleHandle("ntdll.dll"), 
                                     "NtAllocateVirtualMemory" )) ) {
              return ( FALSE );
       }

       else if ( !(NtFreeVirtualMemory = (NTSTATUS (__stdcall *)
                     ( HANDLE, 
                     PVOID *, 
                     PSIZE_T, 
                     ULONG ))
                     GetProcAddress( GetModuleHandle("ntdll.dll"), 
                                     "NtFreeVirtualMemory" )) ) {
              return ( FALSE );
       }

       else return ( TRUE );
}

//
// Otvara i mapira specificiranu datoteku u memoriju.
//

BOOLEAN MapirajDatoteku( LPSTR ime, 
                         DWORD velicina, 
                         PHANDLE hFile, 
                         PHANDLE hMem, 
                         PVOID *pBaznaAdresa )
{
       NTSTATUS      status;
       LARGE_INTEGER secSize;

       *hFile = CreateFile( ime, 
                            GENERIC_READ | GENERIC_WRITE,
                            FILE_SHARE_READ | FILE_SHARE_WRITE,
                            NULL, 
                            OPEN_ALWAYS,
                            0,
                            NULL );

       if ( *hFile == INVALID_HANDLE_VALUE ) {
              return ( FALSE );
       }

       if ( velicina == 0 ) {
              velicina = GetFileSize( *hFile, NULL ); 
       }

       secSize.QuadPart = velicina;

       status = NtCreateSection( hMem, 
                                 SECTION_ALL_ACCESS,
                                 NULL,
                                 &secSize,
                                 PAGE_READWRITE,
                                 SEC_COMMIT,
                                 *hFile );

       if ( status != STATUS_SUCCESS ) {
              CloseHandle( hFile );
              return ( FALSE );
       }

       // da ne bi Mm pomislio da treba mapirati naku nasu adresu...

       *(DWORD*)pBaznaAdresa = NULL;                    

       status = NtMapViewOfSection ( *hMem,
                                     HANDLE (-1),       // trenutni proces
                                     pBaznaAdresa,
                                     0,
                                     velicina,
                                     NULL,
                                     &velicina,
                                     ViewShare,
                                     0,
                                     PAGE_READWRITE );

       if ( status != STATUS_SUCCESS ) {
              NtClose( hMem );
              CloseHandle( hFile );
              return ( FALSE );
       }

       return ( TRUE );
}

//
// Alocira workspace, tj. buffer koji interno koristi kompresijski engine, 
// a ciju velicinu dobivamo sa RtlGetCompressionWorkSpaceSize().
//

BOOLEAN StvoriWorkspace( USHORT CompressionFormat, 
                         USHORT CompressionEngine, 
                         PVOID *hMem)
{
       DWORD         CompressBufferWorkSpaceSize;
       DWORD         CompressFragmentWorkSpaceSize;
       NTSTATUS      status;

       // ne forsiramo alociranje memorije na neku predefiniranu adresu

       *(DWORD*) hMem = NULL;                    

       RtlGetCompressionWorkSpaceSize( CompressionFormat | CompressionEngine,
                                       &CompressBufferWorkSpaceSize,
                                       &CompressFragmentWorkSpaceSize );
       
       status = NtAllocateVirtualMemory( (HANDLE)-1,    // trenutni proces
                                         hMem,
                                         0,
                                         &CompressBufferWorkSpaceSize,
                                         MEM_COMMIT,
                                         PAGE_EXECUTE_READWRITE );

       if ( status != STATUS_SUCCESS ) {
              return ( FALSE );
       }

       return ( TRUE );
}

//
// Kompresira specificirani buffer te ga sprema u specificiranu datoteku.
//

BOOLEAN Kompresiraj( SHORT CompressionFormat, 
                     USHORT CompressionEngine, 
                     PUCHAR NekompresiraniBuf, 
                     DWORD NekompresiranaVelicina,
                     DWORD* KonacnaVelicina, 
                     LPSTR ime_kompresirane_datoteke, 
                     PVOID Workspace )
{
       DWORD         KompresiranaVelicina = DWORD(NekompresiranaVelicina*1.13 + 4);
       HANDLE        hFile;
       HANDLE        hMem;
       PVOID         BaznaAdresa = NULL;
       NTSTATUS      status;

       MapirajDatoteku( ime_kompresirane_datoteke, 
                        KompresiranaVelicina, 
                        &hFile, 
                        &hMem, 
                        &BaznaAdresa );

       status = RtlCompressBuffer( CompressionFormat | CompressionEngine, 
                                   NekompresiraniBuf, 
                                   NekompresiranaVelicina, 
                                   (PUCHAR)BaznaAdresa, 
                                   KompresiranaVelicina,
                                   0,
                                   KonacnaVelicina,
                                   Workspace );

       // zatvori MMF, postavi file pointer na kompresiranu velicinu te zatvori
       // otvorene handleove.

       status = NtUnmapViewOfSection( (HANDLE)-1, BaznaAdresa );
       NtClose( hMem );
       SetFilePointer( hFile, *KonacnaVelicina, 0, 0 );
       SetEndOfFile( hFile );
       NtClose( hFile );

       return ( TRUE );
}

//
// Dekompresira specificirani buffer te ga sprema u specificiranu datoteku.
//
BOOLEAN Dekompresiraj( USHORT CompressionEngine, 
                       PVOID KompresiraniBuffer, 
                       DWORD KompresiranaVelicina, 
                       DWORD* KonacnaVelicina,
                       LPSTR ime )
{
       DWORD         NekompresiranaVelicina        
       LPVOID        NekompresiranBuffer = NULL;
       NTSTATUS      status;
       HANDLE        hFile;
       HANDLE        hMem;
       LPVOID        BaznaAdresa = NULL;
 
       // max. moguca kompresija (92%)
       
       NekompresiranaVelicina = DWORD(KompresiranaVelicina * 12.5);

       status = MapirajDatoteku( ime, 
                                 NekompresiranaVelicina, 
                                 &hFile, 
                                 &hMem, 
                                 &BaznaAdresa );

       _ASSERTE ( status == STATUS_SUCCESS );

       status = RtlDecompressBuffer( CompressionEngine,
                                     (PUCHAR)BaznaAdresa,
                                     NekompresiranaVelicina,
                                     (PUCHAR)KompresiraniBuffer,
                                     KompresiranaVelicina,
                                     KonacnaVelicina );

       _ASSERTE ( status == STATUS_SUCCESS );
       
       status = NtUnmapViewOfSection( (HANDLE)-1, BaznaAdresa );

       _ASSERTE ( status == STATUS_SUCCESS );

       NtClose( hMem );
       SetFilePointer( hFile, *KonacnaVelicina, 0, 0 );
       SetEndOfFile( hFile );
       NtClose( hFile );
       
       return ( TRUE );
}

int _tmain( int argc, _TCHAR* argv[] )
{
       HANDLE               hMem;
       HANDLE               hFile;
       LPSTR                datoteka;
       LPVOID               BaznaAdresa = NULL;
       LPVOID               CompressionWorkspace;
       DWORD                konacna_velicina;
       CHAR                 tempf[MAX_PATH];

       // provjeri da li je dobar broj argumenata

       if ( argc != 3 ) {                        
              Uporaba( argv[0] );
              exit(1);
       }

       char *opcija = argv[1];

       // provjeri da li je opcija dobra

       if ( strcmp(opcija, "-x") && strcmp(opcija, "-d") ) {          
              Uporaba( argv[0] );
              exit(1);
       }

       if ( !LocirajNTDLLFje() ) {
              cerr << "Greska: Ne mogu locirati fje unutar ntdll.dll!" << endl;
              exit(1);
       }
       
       datoteka = argv[2];

       // Uzmi potrebnu velicinu workspace-a i alociraj memoriju za njega.

       if ( !(StvoriWorkspace( COMPRESSION_FORMAT_LZNT1, 
                               COMPRESSION_ENGINE_MAXIMUM, 
                               &CompressionWorkspace )) ) {
              cerr << "Greska: Ne mogu odrediti defaultni workspace za "
                      "kompresiju!" << endl;
              exit(1);
       }

       if ( !(MapirajDatoteku( datoteka, 0, &hFile, &hMem, &BaznaAdresa )) ) {
              cerr << "Greska: Ne mogu mapirati datoteku!" << endl;
              exit(1);
       }

       // Alociraj privremeno ime datoteke u trenutnom direktoriju.

       GetTempFileName( ".", "ble", 0, tempf );

       // Sad trebamo provjeriti da li radimo kompresiju ili dekompresiju.

       if ( strcmp(opcija, "-x") == 0 ) {
              Kompresiraj( COMPRESSION_FORMAT_LZNT1, 
                           COMPRESSION_ENGINE_MAXIMUM, 
                           (PUCHAR)BaznaAdresa, 
                           GetFileSize(hFile, 0), 
                           &konacna_velicina, 
                           tempf, 
                           CompressionWorkspace );
       }

       else { 
              Dekompresiraj( COMPRESSION_FORMAT_LZNT1, 
                             (PUCHAR)BaznaAdresa, 
                             GetFileSize(hFile, 0), 
                             &konacna_velicina, 
                             tempf );
       }

       NtUnmapViewOfSection( (HANDLE)-1, BaznaAdresa );
       NtClose( hMem );
       CloseHandle( hFile );

       DeleteFile( datoteka );
       MoveFile( tempf, datoteka );

       return (0);
}

------------------------------ C O D E  E N D -----------------------------

Ovim jednostavan tool demonstrira gore spomenute fje, te omogucava kompresiranje 
i dekompresiranje proizvoljnih datoteka. Nativna kompresija je prije svega 
zanimljiva vx-erima iz 2 razloga: jako je brza i zahtijeva poziv samo 3 vanjske 
fje! Koliko mi je poznato trenutno ne postoje virusi koji koriste nativnu 
kompresiju iz jednostavnog razloga sto ona nije dosad bila previse 
dokumentirana, i vecina ljudi ni ne zna da ona postoji :>

Tool koristi samo maximalnu kompresiju, pa ce kompresiranje velikih datoteka 
nesto potrajati. Medjutim, nista vas ne sprecava da dodate u program i switch za 
normalnu kompresiju, ukoliko vam zatreba.

Sam crackme koristi samo fju Dekompresiraj(), te na disk dumpa crackme u obliku 
drugog sloja zastite, pod (pseudo)slucajnim imenom, te ga pokrece. Interno je on 
zapisan ne u klasican resurs, vec kao jedan ogromni niz koji ce se na kraju 
spremiti u .data sekciju. Zapis u tom obliku je dobiven koristeci z0mbiev HAXOR 
tool, koji je i napravljen za tu svrhu. Takav se zapis dekompresira u memoriji 
te sprema na disk u temp direktorij, te odatle pokrece sa WinExec(). Da stvar 
bude malchice teze za skuziti, korisim vlastitu implementaciju GetProcAddress() 
i to bez ASCIIZ vec preko hasheva, i to samo za win32API (koji je i u tom obliku 
mogao biti izbjegnut da sam htio, ali i ovako je dovoljno tesko :), dok su 
nativni API-ji inlineirani preko asm-a. Takodjer demonstrira uporabu nativnih 
fja u C/C++ bez potrebe sa parsanjem ntdll.dll:

------------------------------ C O D E ------------------------------------


#include "stdafx.h"	// precompiled headeri koje generira AppWizard
#include <iostream>
#include <windows.h>

// ovdje je spremljen niz sa stage2 BYTE dumpom

#include "stage2.h"

// ovdje su neke klasicne strukutre koje sam vec spomenuo, da ustedimo
// malo prostora.

#include "ntutils.h"

// ovdje su deklaracije fja te inline asm implementacije nativnih fja.

#include "importi.h"

using namespace std;

/ *
 Kad se koristi inline (m)asm preko __asm {..} izgleda da se 1) ne mogu 
 koristiti labele 2) ne radi (ili ja ne znam) implementacija nekih 
 karakteristicnih  snippeta koda preko C-style makroa. Ovo je klasicni @sysenter
 preko makroa, ali  ne radi... mozda nekome od vas proradi... Nadalje, inline 
 asm nazalost ni  ne prepoznaje sysenter opkod, a da stvar bude jos zabavnija, 
 ne zeli asemblirati raw konstante kao kod, pa sam bio prisiljen napraviti dirty 
 hack i morfirati sljedecu instrukciju u letu. Zbog toga atzribut .text sekcije 
 mora biti +w, kako  bi Loader podesio permisije pageva koda da budu writable. 
 Znam, mogao sam to  napraviti rucno sa VirutalProtectEx(), ali mi se ne da... 
 Za tu svrhu se koristi  old skul alatich by Jacky Qwerty zvan pewrsec! Dodajte 
 ga u post-build event u Visual Studio pod opcijama projekta i ne zaboravite da 
 prepoznaje samo 8.3 zapis path-a :)
*/

#define SYSENTER ( a, b ) {								\
	__asm {									\
				call	       $+26					\
				mov		eax, (a)				\
				mov		edx, esp				\
				call	       $+5					\
				pop		ecx					\
				mov		word ptr [ecx+7], 340Fh		\
				push	       0					\
				call	       $-21	       			\
				add		esp, (b*4) + 4			\
	}										\
}

/*
 Uzima bazu ntdll.dll iz PEB bloka. Klasika -> metoda dokumentirana by 
 Ratter/29a, vjerojatno otkriveno od strane ElicZ, ali on nije bas vx koder pa 
 nije znao zasto to iskoristiti pa je sva slava pripala Ratteru :) Nadalje, 
 forsiramo __forceinline kako bi iznervirali lamere koji ne znaju sto je to PEB,
 :)))
*/

__forceinline HMODULE UzmiBazuNtdll()
{
	HMODULE baza;

	__asm {
		mov		eax, dword ptr fs:[30h]     ; PEB baza
		mov		eax, [eax+0ch]              ; PEB_LDR_DATA
		
		; prvi zapis u InitOrderModuleList LIST_ENTRY nizu.
		
		mov		eax, [eax+1ch]              
		mov		eax, [eax+8]                ; jest ntdll.dll
		mov		baza, eax
	}
	return ( baza );
}

//
// Uzima bazu kernel32.dll iz PEB bloka. Ista stvar. Opet forsiramo 
// __forceinline.
//

__forceinline HMODULE UzmiBazuKernel32()
{
	HMODULE baza;

	__asm {
		mov		eax, dword ptr fs:[30h]
              mov		eax, dword ptr [eax+0ch]
              mov		eax, dword ptr [eax+1ch]
              
              ; drugi zapis jest kernel32.dll

		mov		eax, [eax]                  
		mov		eax, dword ptr [eax+08h]
		mov		baza, eax
	}

	return ( baza );
}


/*
 Klasicno hashiranje z0mbievim algoritmom. LSD-ovci su u svom debilnom papiru o 
 win32 shellcode-u, vele, "testirali ga na nekoliko desetaka tisuca primjeraka 
 imena uobicajenih APU-ja i istvrdili da uopce ne daje kolizije". Mislim, 
 debilno. To je ipak z0mbie. Kralj. Za razliku od njih debila koji samo kradu 
 tudji rad i prezentiraju ga u fancy papirima da bi i ostali debili mogli skuzit
 sto majstori rade. Inace asm implementaciju ovog algoritma koristi moja 
 load_dll_address fja koju cemo tek susresti u nastavku :)
*/

DWORD GetHash(LPSTR str)
{
	DWORD h = 0;					// hash algo (x) by z0mbie

	while ( *str ) {
		h = (h << 7) | (h >> 25);		// rol h, 7 u asmu, C sux.
		h ^= (BYTE) *str++;
	}
	return (h);
}

//
// Klasika, parsanje export tablice i racunajne hasha imena API-ja exportanih po 
// imenu i usporedjivanje sa hashom. Tako to virusi rade ;)
//

PVOID MojGetProc(PVOID Baza, DWORD hash)
{
	// uzmi ptr na DOS MZ zaglavlje
	
	PIMAGE_DOS_HEADER mz_zaglavlje = PIMAGE_DOS_HEADER(Baza);
	
	// uzmi ptr na NT zaglavlje
	
	PIMAGE_NT_HEADERS nt_zaglavlje = PIMAGE_NT_HEADERS( LPSTR(Baza) + 
                                          mz_zaglavlje->e_lfanew );

	// dodji do clana niza DataDirectory koji odgovara exportima
	// (nulti clan inace)
	
	PIMAGE_DATA_DIRECTORY export_direktorij = 
	       nt_zaglavlje->OptionalHeader.DataDirectory + 
	              IMAGE_DIRECTORY_ENTRY_EXPORT;
	
	// uzmi informacije o tom direktoriju, velicinu i RVA na koju je mapiran
	
	ULONG velicina = export_direktorij->Size;
	ULONG adresa = export_direktorij->VirtualAddress;
	
	// normaliziraj adresu
	
	PIMAGE_EXPORT_DIRECTORY exporti = PIMAGE_EXPORT_DIRECTORY( PCHAR(Baza) 
	                                                           + adresa );
	
	PULONG funkcije = PULONG(PCHAR(Baza) + exporti->AddressOfFunctions);
	
	PSHORT redni_brojevi = PSHORT( PCHAR(Baza) + 
	                               exporti->AddressOfNameOrdinals );
	
	PULONG imena_fja = PULONG(PCHAR(Baza) + exporti->AddressOfNames);

	PVOID adresa_apija = 0;

	for (ULONG i = 0; i < exporti->NumberOfNames; i++) {

		// iteriraj za svako ime exportanog API-ja
		
		ULONG redni_broj = redni_brojevi[i];

		if ( funkcije[redni_broj] < adresa || 
		       funkcije[redni_broj] >= adresa + velicina) {

			// ako smo pronasli API koji trazimo po imenu, 
			// uzmi njegov RVA i normaliziraj ga.
			
			if (GetHash(LPSTR(PCHAR(Baza) + imena_fja[i])) == hash)
				adresa_apija = PCHAR(Baza) + funkcije[redni_broj];
		}
	}
	return adresa_apija;
}

/*
 Fja trazi adrese fja koje nam trebaju, te vraca FALSE ukoliko se bar jedna od 
 njih nije mogla naci. Hashe-ve nalazimo tako sto unutar koda kad debuggiramo
 vidimo sto ce nam GetHashCode() vratiti za ime API-ja. Mogao sam staviti 
 direktno, ali bi se onda direktno unutar koda mogli vidjeti kojeg se API-ja 
 adresa trazi preko raw ACIIZ stringa koji je parametar za GetHashCode(), ovako 
 je bar malchice tezhe :) I ne trazi samo za NTDLL vec i za kernel32, naziv je
 ostao zbog "povijesnih razloga" ;)
*/

BOOLEAN LocirajNTDLLFje()
{
	if ( !(RtlDecompressBuffer = (NTSTATUS (__stdcall *)
                     ( USHORT,
                       PUCHAR,
                       ULONG,
                       PUCHAR,
                       ULONG,
                       PULONG ))
              MojGetProc( UzmiBazuNtdll(), 0x9daf495f )) ) {
              return ( FALSE );
	}

	if ( !(fCreateFileA = (HANDLE (__stdcall *)
	              ( LPCSTR,
	                DWORD,
	                DWORD,
	                LPSECURITY_ATTRIBUTES,
	                DWORD,
	                DWORD,
	                HANDLE ))
              MojGetProc( UzmiBazuKernel32(), 0x08f8f114 )) ) {
		return ( FALSE );
	}

	if ( !(fSetFilePointer = (DWORD (__stdcall *)
	              ( HANDLE,
	                LONG,
	                PLONG,
	                DWORD ))
              MojGetProc( UzmiBazuKernel32(), 0xef48e03a )) ) {
		return ( FALSE );
	}

	if ( !(fSetEndOfFile = (BOOL (__stdcall *)(HANDLE))
	       MojGetProc( UzmiBazuKernel32(), 0x2d0d9d61 )) ) {
		return (FALSE);
	}

	if ( !(fWinExec = (UINT (__stdcall *)
	              ( LPCSTR,
	                UINT ))
              MojGetProc( UzmiBazuKernel32(), 0xe8bf6dad )) ) {
		return ( FALSE );
	}

	if ( !(fGetTempFileNameA = (UINT (__stdcall *)
	              ( LPCSTR,
	                LPCSTR,
	                UINT,
	                LPSTR ))
              MojGetProc( UzmiBazuKernel32(), 0x0fa4f502 )) ) {
		return ( FALSE );
	}
	
	if ( !(fGetTempPathA = (DWORD (__stdcall *)
	              ( DWORD,
	                LPSTR ))
              MojGetProc( UzmiBazuKernel32(), 0x58fe7abe )) ) {
		return ( FALSE );
	}
	
	if ( !(fGetFileSize = (DWORD (__stdcall *)
	              ( HANDLE,
	                LPDWORD))
              MojGetProc( UzmiBazuKernel32(), 0xaef7cbf1 )) ) {
		return ( FALSE );
	}		

	if ( !(fDeleteFileA = (BOOL (__stdcall *)(LPCSTR))
	       MojGetProc( UzmiBazuKernel32(), 0x81f0f0df )) ) {
		return (FALSE);

	else return ( TRUE );
}

//
// Otvara i mapira specificiranu datoteku u memoriju. Gotovo identicno prosloj 
// implementaciji samo sto sad koristim f* fje te NtClose() umjesto 
// CloseHandle() :)
//

BOOLEAN MapirajDatoteku( LPSTR ime, 
                         DWORD velicina, 
                         PHANDLE hFile, 
                         PHANDLE hMem, 
                         PVOID *pBaznaAdresa )
{
	NTSTATUS status;
	LARGE_INTEGER secSize;
	HANDLE Handle = NULL;

	*hFile = fCreateFileA( ime, 
				  GENERIC_READ | GENERIC_WRITE,
				  FILE_SHARE_READ | FILE_SHARE_WRITE,
				  NULL, 
				  OPEN_ALWAYS,
				  0,
				  NULL );

	if ( *hFile == INVALID_HANDLE_VALUE ) {
		return (FALSE);
	}

	if ( velicina == 0 ) {
		velicina = fGetFileSize( *hFile, NULL ); 
	}

	secSize.QuadPart = velicina;

	status = NtCreateSection( hMem, 
			            SECTION_ALL_ACCESS,
			            NULL,
				     &secSize,
				     PAGE_READWRITE,
				     SEC_COMMIT,
				     *hFile );

	if ( status != STATUS_SUCCESS ) {
		NtClose( hFile );
		return ( FALSE );
	}

       // da ne bi Mm pomislio da treba mapirati neku nasu adresu..

	*(DWORD*)pBaznaAdresa = NULL;		

	status = NtMapViewOfSection ( *hMem,
					  HANDLE (-1),       // trenutni proces
					  pBaznaAdresa, 
					  0,
					  velicina,
				         NULL,
					  &velicina,
					  ViewShare,
					  0,
					  PAGE_READWRITE );

	if ( status != STATUS_SUCCESS ) {
		NtClose( hMem );
		NtClose( hFile );
		return ( FALSE );
	}

	return ( TRUE );
}

//
// Dekompresira specificirani buffer te ga sprema u specificiranu datoteku. ovi 
// _ASSERTE makroi ce nestati u Debug buildu, samo da napomenem ukoliko jos ne 
// znate :>
//

BOOLEAN Dekompresiraj( USHORT CompressionEngine, 
                       PVOID KompresiraniBuffer, 
                       DWORD KompresiranaVelicina, 
                       DWORD* KonacnaVelicina,
			  LPSTR ime )
{
	DWORD		NekompresiranaVelicina;
	LPVOID		NekompresiranBuffer = NULL;
	NTSTATUS	status;
	HANDLE		hFile;
	HANDLE		hMem;
	LPVOID		BaznaAdresa = NULL;

       // max. moguca kompresija (92%)

       NekompresiranaVelicina = DWORD( KompresiranaVelicina * 12.5 );		

	status = MapirajDatoteku( ime, 
	                          NekompresiranaVelicina, 
	                          &hFile, 
	                          &hMem, 
	                          &BaznaAdresa );

	_ASSERTE ( status == TRUE );

	status = RtlDecompressBuffer( CompressionEngine,
					  (PUCHAR)BaznaAdresa,
					  NekompresiranaVelicina,
					  (PUCHAR)KompresiraniBuffer,
					  KompresiranaVelicina,
					  KonacnaVelicina );

	_ASSERTE ( status == STATUS_SUCCESS );
	
	status = NtUnmapViewOfSection( (HANDLE)-1, BaznaAdresa );

	_ASSERTE ( status == STATUS_SUCCESS );

	NtClose( hMem );
	fSetFilePointer( hFile, *KonacnaVelicina, 0, 0 );
	fSetEndOfFile( hFile );
	NtClose( hFile );
	
	return (TRUE);
}

int _tmain( int argc, _TCHAR* argv[] )
{
	DWORD			konacna_velicina;
	CHAR			tempf[MAX_PATH];
	CHAR			tempp[MAX_PATH];

	if ( !LocirajNTDLLFje() ) {
		exit(1);
	}
	
	// Alociraj privremeno ime datoteke u temp direktoriju, sa prefiksom 
	// " #_", ala ce bit veselo ako netko ovo vidi u Task Manageru ;)

	fGetTempPathA(MAX_PATH, tempp);
	fGetTempFileNameA( tempp, " #_", 0, tempf );

       // Dekompresiraj u nju i pokreni!

	Dekompresiraj( COMPRESSION_FORMAT_LZNT1, 
	              (PUCHAR)pakirana_datoteka, 
	              pakirana_datoteka_size, 
	              &konacna_velicina, 
	              tempf );

	fWinExec(tempf, SW_SHOWDEFAULT);
	
	// brisi fajl da ga netko ne bi analizirao :)
	
	fDeleteFileA( tempf );

	return (0);
}

------------------------------ C O D E  E N D -----------------------------

Jos je samo ostalo da objasnim na primjeru kako su direktno u asmu 
implementirani nativni API-ji, kao sto je vec spomenuto, i kao sto mozete 
vidjeti uostalom u import.h u prilozenom izvornom kodu, NtCreateSection() je 
npr. implementiran ovako:

__forceinline NTSTATUS NTAPI NtCreateSection ( 
    OUT PHANDLE			SectionHandle,
    IN ACCESS_MASK			DesiredAccess,
    IN POBJECT_ATTRIBUTES	       ObjectAttributes		       OPTIONAL,
    IN PLARGE_INTEGER		MaximumSize				OPTIONAL,
    IN ULONG				SectionPageProtection,
    IN ULONG				AllocationAttributes,
    IN HANDLE				FileHandle				OPTIONAL
     )
{
	__asm {
			push	       FileHandle
			push	       AllocationAttributes
			push	       SectionPageProtection
			push	       MaximumSize
			push	       ObjectAttributes
			push	       DesiredAccess
			push	       SectionHandle

			call	       $+26										
			mov		eax, 32h
			mov		edx, esp
			call	       $+5
			pop		ecx
			mov		word ptr [ecx+7], 340F0
			push	       0
			call	       $-21
			
			add		esp, 7*4 + 4
	}
}

Prije svega je vazno uociti __forceinline kojim kompajler *forsiramo* da 
inline-ira ovu fju, tj. da *ne* generira stanardan kod za prolog i epilog, te da 
ne stavlja parametre na stog umjesto nas. To nam je potrebno pije svega jer 
prije poziva sysenter edx mora biti ptr na parametre push-ane na stog, pa ne 
zelimo nista ostaviti slucaju sta kompajler misli dodavati izmedju dijela koda  
kojim poziva neku nasu fju koja radi inline asm sysenter i dijela kad se to 
stvarno dogadja. Dakle uglavnom za eliminiranje potencijalnih bugova, a bogami i 
za ustedu prostora te da onome tko reversa temeljito oteza posao :)

Kao sto sam vec rekao, glupi inline asm ne prepoznaje sysenter instrukciju, pa u 
letu patchamo onaj dummy "push 0", sa 340F0h sto predstavlja opkod za sysenter, te 
osim toga call-ove pravimo relativno na location counter posto ne smijemo ni 
labele koristiti. Ako nista drugo kod je jebeno l33t :)


///////////////////////////////////////////////////////////////////////////
--[ Phun with CLR
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

Drugi je sloj zastite program napisan u C# i obsfukiran sa Xenocode 2005. Taj 
program u C# je zanimljiv iz nekoliko razloga, jer demonstrira uporabu anti-* 
trikova u managed kodu te kako ebmeddati asm kod u bilo koji managed jezik.

Krenimo od AntiDebug.cs datoteke te AntiDebug klase koja se u njoj nalazi. 
Ta je klasa sealed i ima privatni konstruktor, zato jer su sve metode u njoj 
staticke. Sadrzi sljedece metode: ChechBadDriverCreateFile() 
CheckSysDebuggerCreateFile() te ProvjeriBpx(). Te fje koriste P/Invoke za 
sljedeci win32 API: LoadLibrary(), FreeLibrary(), GetProcAddress(), 
CloseHandle() te CreateFile(), te su jos u istoj toj regiji koda deklarirane 
neke win32 konstante koji ti API-ji koriste. Inace, gomilu signatura za P/Invoke 
mozete naci na [4]. 

CheckSysDebuggerCreateFile() metoda radi jaaako star i poznat trik za detekciju 
sistemskih (kernel-mode debuggera). Trenutno postoje 3 (koliko ja znam): 
SotfIce, TRW2000 (ovaj je samo za win9x) te Syser. Ovaj zadnji sam otkrio tek 
prije 3 dana i izgleda da malo tko zna trenutno za njega. Inace, koliko sam ja 
shvatio rade ga 2 kineza i cilj im je napraviti neku verziju super-softice. 
Zasad im to i vise nego uspjeva jerbo syser ima GUI koji je za klasu ispred sice
-ovog, ima tabove, fine boje, i on prima pluginove i za razliku od softice, na 
XP SP2 se, barem kod mene, pokazao savrseno stabilan pri trace-anju ring0 koda. 
Mozete vise informacija vidjeti na ES forumu na temi [5], te tamo se nalazi 
linkovi na direktan download.

Syser nisam dodao iz jednostavnog razloga jer ne znam kako bi ste drugacije 
trace-ali ring0 kod, premda ima jos jedna forica ali nije sad vazno, i sto 
sam ga otkrio tek nakon sto je ovaj sloj u cijelosti napisan. Ali dobro sad, 
provjera za aktivnim SyserApp.exe bogznakakva kvantna fizika, a cini se da i
driver komponenta sjebe dosta stvari (noskrnl.exe se nekad zamijeni sa 
kernel1.exe, nesto prcka i sa bazom IDT tablice i jos neke sitnice...).

    /// <summary>
    /// Provjerava da li je SoftIce/TRW2000 driver loadan preko CreateFileA() trika.
    /// </summary>
    public static bool CheckSysDebuggerCreateFile()
    {
        string[] bad_drivers = { @"\\.\SICE", @"\.\SICE", @"\\.\NTICE", @"\.\NTICE", @"\\.\TRW", @"\\.\TRWDEBUG" };

        foreach ( string device in bad_drivers ) {
            int hmod = CreateFile(device,             // File name
                GENERIC_READ | GENERIC_WRITE,         // Desired access
                FILE_SHARE_READ | FILE_SHARE_WRITE,   // Share mode
                ( IntPtr )0,                          // Attributes
                OPEN_EXISTING,                        // Creation disposition
                FILE_ATTRIBUTE_NORMAL,                // Flags and attributes
                ( IntPtr )0);                         // Template file

            if ( hmod != INVALID_HANDLE_VALUE ) {
                CloseHandle(hmod);
                return true;
            }
        }

        return false;
    }

Dakle, u bad_drivers spremamo stringove imena drivera sistemskih debuggera, te 
ih odreda pokusavamo otvoriti sa CreateFileA(), te ako uspijemo, tada je jedan
od njih aktivan, tj. ne dopusta "exclusive" mod pristupa. "\\.\SICE" je verzija
za win9x, "\\.\NTICE" za NT-bazirane win. TRW2000 radi samo na win9x pa ima samo 
jednu verziju :>

ChechBadDriverCreateFile() fja radi slicnu stvar, ali za drivere koji koriste
popularni alaitchi regmon, filemon te icedump tool koji sluzi za dump memorije 
u datoteku (ne zelimo spijune :)

   public static bool ChechBadDriverCreateFile()
    {
        string[] bad_drivers = { @"\\.\FILEMON", @"\\.\REGMON", @"\\.\ICEDUMP" };

        foreach ( string device in bad_drivers ) {
            int hmod = CreateFile(device,                   // File name
                GENERIC_READ | GENERIC_WRITE,               // Desired access
                FILE_SHARE_READ | FILE_SHARE_WRITE,         // Share mode
                ( IntPtr )0,                                // Attributes
                OPEN_EXISTING,                              // Creation disposition
                FILE_ATTRIBUTE_NORMAL,                      // Flags and attributes
                ( IntPtr )0);                               // Template file

            if ( hmod != INVALID_HANDLE_VALUE ) {
                CloseHandle(hmod);
                return true;
            }
        }
        return false;
    }

Fja ProvjeriBpx() radi sto joj i ime kaze, provjerava breakpoint na zadanom 
API-ju koji se nalazi u zadanom DLL-u. Prvo se zadani DLL mapira u nas adresni
prostor, te nakon toga u temp_buf kopiramo prvih 13 bajtova sa adrese API-ja te
provjeravamo da li se na nekom od njih nalazi 0xCC, tj. opkod za "int 3" koji 
debuggeri umecu na dio koda na kojem postavljas breakpoint.

   public static bool ProvjeriBpx(string ImeDLL, string API)
    {
        bool ima_bpx = false;
        byte[] temp_buf = new byte[13];

        IntPtr hLib = LoadLibrary(ImeDLL);
        IntPtr adresa_API = GetProcAddress(hLib, API);

        if ( hLib.ToInt32() == 0 || adresa_API.ToInt32() == 0 ) {
            System.Diagnostics.Debugger.Log(0, "1",
              String.Format(CultureInfo.InvariantCulture, "badAPICall: ", API, ImeDLL));
            return false;
        }

        Marshal.Copy(adresa_API, temp_buf, 0, temp_buf.Length);

        for ( int i = 0; i < temp_buf.Length - 1; i++ )
            if ( temp_buf[i] == 0xCC ) ima_bpx = true;

        FreeLibrary(hLib);
        return ima_bpx;
    }

Krenimo sad na Crackme.cs gdje je glavna akcija. Sad ide prvo malo teorije o 
jednoj jako kul stvarchici - inline asm u C#. Nazalost nije to pravi inline 
asm, niti C# podrzava inline MSIL, vec jedan dirty hack :>

///////////////////////////////////////////////////////////////////////////
--[ Tko kaze da nema asemblera u .NET-u?
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

Delegati su u C# tipovi izvedeni iz System.MulticastDelegate i predstavljaju 
type-safe verziju C-style pokazivaca na funkcije.Pri deklaraciji delegata 
nemoguce je specificirati njihovu implementaciju (moguce je u MSIL, premda ne u 
C# direktno), drugim rijecima pruza je sam CLR. U biti oni za sam koncept CLR-a 
predstavljaju neku apstrakciju standardnih fja koje pozivaju neke druge fje.

Pri deklaraciji recimo sljedeceg delegata:

       private delegate uint DummyDelegat(uint a);

C# kompajler generira klasu koja je izvedena iz System.MulticastDelagate sa 
metodama .ctor (defaultni konstruktor), BeginInvoke, Invoke i EndInvoke, MSIL 
disasm preko .NET Reflectora izgleda ovako:

.class nested private auto ansi sealed DummyDelegat
      extends [mscorlib]System.MulticastDelegate
{
      .method public hidebysig specialname rtspecialname instance void .ctor(object 'object', native int 'method') runtime managed
      {
      }

      .method public hidebysig newslot virtual instance [mscorlib]System.IAsyncResult BeginInvoke(unsigned int32 a, 
                                          [mscorlib]System.AsyncCallback callback, object 'object') runtime managed
      {
      }

      .method public hidebysig newslot virtual instance unsigned int32 EndInvoke([mscorlib]System.IAsyncResult result) runtime managed
      {
      }

      .method public hidebysig newslot virtual instance unsigned int32 Invoke(unsigned int32 a) runtime managed
      {
      }

}

Ove su metode "runtime managed", sto znaci da je CLR odgovoran za njihovu 
implementaciju. Mada neki misle da je nemoguce, MOGUCE je pruziti njihovu 
vlastitu implementaciju u MSIL tako sto cemo "runtime managed" dijelove 
promijeniti u "cil managed" (CIL = Common Intermediate Language, po ECMA 
standardu, MSIL je MS-ova implementacija istog). Recimo ovako:

.method public hidebysig newslot virtual instance unsigned int32 Invoke(unsigned int32 a) cil managed
{
    ldstr   "Pozdrav iz delegata!"   
    call    void [mscorlib]System.Console::WriteLine(string)
    ret
}

Stoga, ako ne volite ove "runtime managed" crne kutije, uvijek mozete pruziti 
vlastitu implementaciju. Doduse, nece proci PEVerfiy test, a ovaj hack ne radi 
ni recimo pod mono-om. Ko ga jebe, glavno da radi na .NET ;)

E sad se vi pitate sto je tako zanimljivo u vezi delegata i koji ih ja jebeni 
kurac spominjem. E pa spominjem ih jebeno zato, sto cemo iskoristiti jebeni 
nacin na koji su jebeni delegati implementirani, da izvedemo asm kod iz C# 
direktno, bez pozivanja jebenih vanjskih unmanaged win32 API-ja.

Pokrenimo .NET reflector i napravimo RCE od System.Delegate. Uocimo pri dnu 
polja:

       ...
      
      // Privatna polja
      private RuntimeMethodInfo _method;
      private IntPtr _methodPtr;
      private IntPtr _methodPtrAux;
      private object _target;
}

Logicno bi bilo zakljuciti da _methodPtr i _methodPtrAux pokazuju na metodu koju 
instancirani delegat enkapsulira, sto ce se na kraju i pokazati i kao istina :>

Nakon malo prckanja po Rotoru [6] i malo disasma dosao sam do sljedeceg 
zakljucka: U slucaju da je staticka metoda proslijedjena delegatu kao parametar, 
methodPtrAux bi sadrzavalo ono sto bi inace sadrzavalo _methodPtr polje, a sam 
_methodPtr pokazuje na mali stub koji mice "this" pokazivac prije nego skace na 
_methodPtrAux. Dakle, kontroliranjem ta 2 polja (u slucaju da se radi o 
statickoj metodi, posve je svejedno kojeg), mozemo napraviti redirekciju 
_methodPtr na neku proizvoljnu lokaciju, recimo hrpu bajtova koji predstavljaju 
neki nas nasty asm kod.

Izgleda da CLR interno koristi __fastcall konvenciju (koju recimo po defaultu 
koristi bcc32), sto ce reci da su parametri proslijedjeni kroz registre (redom 
edx, ecx..), zato napravimo ovakav kod:

        static byte[] asm_kod =  {
                0xCC,               // int 3
                0x89,0xD0,          // mov eax, edx
                0xC3                // retn
        };

Dakle nekakva fja koja prima jedan parametar (u edx), vraca ga u eax, a prije 
toga sam stavio int3 kako bismo mogli sve to pratiti u olly-u :>

Nadalje, buduci da cemo raditi sa unsafe kodom, bilo bi korisno (a i moramo jer 
ce inace C# kompajler prijaviti gresku!) dodijeljivanje adrese ovog koda nekom 
proizvoljnom pokazivacu staviti pod "fixed" komandom, koja sprecava GC da 
napravi relokaciju buffera ciju adresu uzimamo, kao rezultat neke interne 
"optimizacije" izgleda memorije (.NET GC je generacijski, pa je cak ova 
situacija vrlo izgledna!) da bi sprijecio njezinu fragmentaciju.


Dakle, ovako nekako:

fixed ( byte* adresa = asm_kod )
{
    ...
}

Unutar tijela fixed naredbe GC *sigurno* nece relocirati asm_kod[] buffer!

Da bismo napravili redirekciju _methodPtrAux/_methodPtr polja morat cemo 
koristiti mehanizme refleksije:

       // Uzmi enkapsulaciju tipa u varijablu
       
       Type delegat = typeof(System.Delegate);
       
       // Uzmi FieldInfo strukturu koja sluzi za opis nekog polja (field) tipa, 
       // umjesto _methodPtrAux moze biti i _methodPtr posto je DummyMetoda 
       // staticka! NonPublic zastavica posto su ta polja privratna i Instance 
       // posto nisu staticka!
       
       FieldInfo MethodPtr = delegat.GetField("_methodPtrAux", BindingFlags.NonPublic | BindingFlags.Instance);
       
       // stvorimo delegat oko neke dummy metode koja nista ne radi
       
       DummyDelegat d = new DummyDelegat(DummyMetoda);

       // bejbe!
       
       MethodPtr.SetValue(d, ( IntPtr )adresa);


Ova dummy metoda moze raditi bilo sta, vazno je samo da prima 1 argument koji
u asm kodu citamo iz edx:

static uint DummyMetoda(uint a)
{
    return 0;
}

E sad, cijeli program bi izgledao ovako nekako:

namespace asm_in_cis
{
    class Glavna
    {
        delegate uint DummyDelegat(uint a);
        static uint DummyMetoda(uint a)
        {
            return 0;
        }

        static byte[] asm_kod =  {
                0xCC,           // int 3
                0x89,0xD0,      // mov eax, edx
                0xC3            // retn
        };

        unsafe static void Main(string[] args)
        {
            fixed ( byte* adresa = asm_kod ) {
                Type delegat = typeof(System.Delegate);
                FieldInfo MethodPtr = delegat.GetField("_methodPtrAux", BindingFlags.NonPublic | BindingFlags.Instance);
                DummyDelegat d = new DummyDelegat(DummyMetoda);
                MethodPtr.SetValue(d, ( IntPtr )adresa);
                Console.WriteLine(d(5));
            }
        }
    }
}

Pri cemu dodajemo poziv Console::WriteLine() jer ce inace cijeli Main() biti 
dead code koji ce kompajler optimizirati tako sto ce ga maknuti :)

Sad mozemo ovako kompajliran program otvoriti pod olly-em, i vidjeti gdje cemo 
"sletjeti":

00B12BFF   00CC             ADD AH,CL
00B12C01   89D0             MOV EAX,EDX
00B12C03   C3               RETN

Dakle, JIT-er je kod kompajlirao negdje na hrpi i direktno ga pozvao. Na ovaj 
nacin mozemo cisto iz nekog managed jezika pozivati asm kod, bez potrebe 
pozivanja nekih unmanaged API-ja tipa CreateThread().

Sta sve mozemo na ovaj nacin? Pa prije svega, mozemo u kod embeddati sto god 
hocemo, a sto je prije napisano u asm-u na offset-independant nacin, konkretno 
mislim na virus :) Recimo, virus bi mogao ildasm-ati neki .NET asemblij, ubaciti
ovih 10 linija koda na proizvoljnu lokaciju za koju zna da ce se pozvati (recimo 
prije Main), pri cemu ce izvedeni kod biti tijelo virusa napisanog u win32asm. 
Nadalje, moguca je i direktna infekcija bez ildasm-anja tako sto ce se patchati
u letu neki MSIL opkodi te napraviti redirekcija na dodano tijelo virusa...ili 
ako je tijelo virusa preveliko, napravimo u asm-u stub koji ce potraziti tijelo 
virusa append-ano na asemblij, mapirati ga na proizvoljnu lokaciju u memoriji i
opaliti CreateThread()....mogucnosti su u glavnom nebrojene.

Jedini je zajeb sto moramo prvo testirati kod u odvojenom asembleru pa ga onda 
dodati u C# kod koristeci recimo z0mbijev HAXOR tool.

Nadalje, ovime su otvorene nebrojene mogucnosti u internom manipuliranju CLR-om. 
Ideje poput ".NET globalne rezidentnosti", "infekcije GAC-a u letu" i sl. bi 
ubrzo mogle postati stvarnost.

A sad se vratimo na Crackme.cs. Krenimo od Main() metode. TESTING define (a la 
C-style pretprocesor, mada C# nema takvu vrstu pretprocesora) koristi za debug
svrhu te onemogucava anti-debug dijelove crackme-ja.

     unsafe static void Main(string[] args)
        {

#if !TESTING

Sad ide dio koda kojim provjeravamo da li se izvodimo pod VM ili je VM jedan od 
aktivnih "pravih"/virtualnih mreznih adaptera. Naime, ima jedna kul forica, a to 
je da su i VMWare i VirtualPC (MS) morali registirati prva 24 bita 
(vendor-defined) MAC adrese mreznih adaptera koje "stvaraju"/emuliraju, te tu 
cinjenicu koristimo protiv njih :) U [7] mozete vidjeti po kompanijama koje su 
MAC adrese pod kojom kompanijom registirane. Nas konkretno zanimaju 2 
najpopularnije virtualne masine na win, a to su VMWare i VirtualPC:

IEEE registrirane MAC adrese za VMware su:

00:0C:29:XX:XX:XX
00:05:69:XX:XX:XX
00:50:56:XX:XX:XX

a za Microsoft (valjda za VirtualPC, nisam isprobao doduse..jerbo VMware rox :)

00:0D:3A:XX:XX:XX
00:12:5A:XX:XX:XX
00:50:F2:XX:XX:XX
00:03:FF:XX:XX:XX

Ta se inace prva 3 bajta zovu OUI (Organizationally Unique Identifier) te ih
dodijeljuje IANA (Internet Assigned Numbers Authority) i publiciraju se u RFC
dokumentima (RFC 1700).

E sad, maske za ove adrese sve strpavamo u polje stringova bad_mac[] te redom 
enumeriramo sve mrezne adapter koji imaju postavljenu IP adresu (sl. kao output 
komande ipconfig), te u tu svrhu koristimo WMI koji je pod .NET-om izlozen preko 
System.Management namespece-a.

Za enumeriranje se koriste SQL-like upiti, a za tocan raspored u WMI 
hijerarhiji mozete vidjeti u WMI Object Browseru [8]. Nas konkrentno zanima 
Win32_NetworkAdapterConfiguration klasa koja ima svojstvo IPEnabled postavljeno 
na TRUE, tj. mrezni adapter sa dodijeljenom IP adresom, pa koristimo sljedeci 
upit:

"SELECT * FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = 'TRUE'"

Nadalje, "IPAddress" svojstvo sadrzi listu IP adresa dodijeljenih adapteru, te 
njih sve skupa, za sve adaptere ubacujemo u kolekciju tipa ArrayList, te nakon 
toga obradjujemo:

foreach ( string s in adrese_svih_adaptera ) {
  IPHostEntry hostInfo = Dns.GetHostByAddress(s);
  foreach ( IPAddress ip in hostInfo.AddressList ) {
      byte[] ab = new byte[6];
      int len = ab.Length;

      // izracunaj fizicku adresu
      
      int r = SendARP(( int )ip.Address, 0, ab, ref len);
      string mac = BitConverter.ToString(ab, 0, 6);
      foreach (string bad_mac_maska in bad_mac) {
          if ( mac.StartsWith(bad_mac_maska) )
          Process.GetCurrentProcess().Kill();
       }
  }
}

Fizicku smo adresu takodjer mogli uzeti iz MAC objekta, ali nju je vrlo lako 
spoofati na razini samog WMI, pa zato koristi P/Invoke-ani iphlpapi SendARP() 
koji na zeljenu adresu salje ARP zahtijeve te vraca MAC adresu primatelja u 
pMacAddr:

DWORD SendARP(
  IPAddr DestIP,
  IPAddr SrcIP,
  PULONG pMacAddr,
  PULONG PhyAddrLen
);

Nakon toga nas zanimaju vendor-specific prvih 24 bita te rezultat pretvaramo u 
string te u petlji kompariramo sa "zlocestim" MAC adresama u bad_mac_maska nizu. 
Ukoliko je u njoj, koljemo aktivni proces od crackme-a.

Nakon toga koristimo provjeru u registry-u za VMWare/VirtualPC-specific 
kljucevima:

     // Provjera za VirtualPC
     RegistryKey rk = Registry.ClassesRoot.OpenSubKey("Virtual.Machine.VMC");
     if ( rk != null ) {
         rk.Close();
     }

     // Provjera za VMware
     rk = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\VMware, Inc.");
     if ( rk != null ) {
         rk.Close();
         Process.GetCurrentProcess().Kill();
     }

     if (System.Diagnostics.Debugger.IsAttached == true )
         Process.GetCurrentProcess().Kill();

Ovi su kljucevi pristuni i na stvarnom i na emuliranom OS-u.

Nakon toga radimo pozivamo anti-dump kod:

fixed ( byte* adresa = AntiProcDump ) {
  Type delegat = typeof(System.Delegate);
  FieldInfo MethodPtr = delegat.GetField("_methodPtrAux", BindingFlags.NonPublic | BindingFlags.Instance);
  DummyDelegat AntiDump = new DummyDelegat(DummyMetoda);
  MethodPtr.SetValue(AntiDump, ( IntPtr )adresa);
  AntiDump(0);
}

Pri cemu se u AntiProcDump polju bajtova koje izvodimo nalazi sljedeci asm kod:

                     mov	eax, fs:[TEB_PEB]
                     mov	eax, [eax.PEB_PebLdrData]
                     mov    eax, [eax+0Ch]
                     add    dword ptr [eax+20h], 3137h 
                     xor    eax, eax
                     retn

tj. povecavamo svoju virtualnu velicinu u LDR_MODULE strukturi polje SizeOfImage 
za 3137h pri cemu ce dumpanje programa sa nekim externim alaticem a la procdump 
uzrokovati page fault, jer ce program dumpati dijelove memorije koje ne postoje (a koje misli da su alocirane od strane programa). LDR_MODULE je interna 
struktura koju inace koristi NT Loader i nije direktno dostupa preko win32API,
pa ce svi dumper koji ga koriste pogresno iscitati stvarnu velicinu image-a
datoteke.

Nakon toga se poziva ClearDebuggerFlag blok koda koji resetira u PEB bloku 
DebuggerPresent polje, a sto opet ni nema nekog smisla :), samo sluzi za 
zbunjivanje crackera... :> Taj se dio koda naime poziva ovako:

fixed ( byte* adresa = ClearDebuggerFlag ) {
  Type delegat = typeof(System.Delegate);
  FieldInfo MethodPtr = delegat.GetField("_methodPtrAux", BindingFlags.NonPublic | BindingFlags.Instance);
  DummyDelegat ClearDebugFlag = new DummyDelegat(DummyMetoda);
  MethodPtr.SetValue(ClearDebugFlag, ( IntPtr )adresa);

  for (int i = 0; i < 256; i++) {
      uint k = (uint)(i * 256 / Math.PI);
      k ^= (uint)AppDomain.CurrentDomain.GetHashCode();
      k += (uint)Thread.CurrentThread.GetHashCode();
      ClearDebugFlag((uint)(k-i));
      ClearDebugFlag((uint)(k + i));
      ClearDebugFlag((uint)(k * i));

      byte[] heh = new byte[ClearDebuggerFlag.Length];
      for ( int j = 0; j < heh.Length; j++ )
          heh[j] = ClearDebuggerFlag[j];

      DummyDelegat ClearDebugFlag2 = new DummyDelegat(DummyMetoda);
      fixed ( byte* adresa2 = heh ) {
          MethodPtr.SetValue(ClearDebugFlag2, ( IntPtr )adresa2);
          ClearDebugFlag2(( uint )( k * Math.Pow(Math.E, Math.PI) ));
      }
    }
    

uint k je cisto dummy i sluzi za tipicnu obsfukaciju, bas kao i operacije u 
petlji sa i. byte[] polje h se alocira dinamicki te se u njega kopira 
ClearDebuggerFlag bajt po bajt, opet se alocira delegat i poziva...sve ovo sluzi 
za zbunjivanje crackera te da ga odvrati od primarne svrhe programa.

Nakon toga kreiramo nit u kojoj ce se pozivati anti-trace kod te izlazimo iz 
fixed bloka:

       
       Thread at = new Thread(new ThreadStart(AntiTrace));
       at.Priority = ThreadPriority.Normal;
       at.Start();
}

Pogledajmo sto se u toj statickoj metodi krije:

 static void AntiTrace()
 {

I ova metoda sadrzi gomilu koda kojem je svrha cista obsfukacija :) Prvo 
inicijaliziramo CSP koji koristimo za generiranje kriptografski-sigurnih 
pseudoslucajnih brojeva:
 
     RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
     byte[] b = new byte[1];

Sad dolazi dio koda u kojoj testiramo da li je attachan CLR debugger, te je,
normalno, taj dio koda *izvan* TESTING define-a, kako bismo mogli debuggirati
nas vlastiti kod :) Ovo je managed verzija IsDebuggerPresent() API-ja!

Inace, standardno se kod moze debuggirati ili pod Visual Studiom, ili pod 
CLR debuggerom kojeg mozete naci u:

X:\Program Files\Microsoft.NET\SDK\v1.1\GuiDebug\DbgCLR.exe

Ukoliko imate .NET SDK [9] instaliran u taj direktorij, te ce naveden
primjerak koda uspjesno detektirati debugging pod njim:


#if !TESTING
     if (System.Diagnostics.Debugger.IsAttached == true )
         Process.GetCurrentProcess().Kill();
#endif

E sad u beskonacnoj petlji radimo sljedece: ucitavamo pseudoslucajan broj te iz 
njega izvlacimo broj novih niti koje stvaramo, od 0 do 16. Nove niti imaju 
entrypint u metodi AntiTrace::ProvjeriTrace i AntiTrace::ProvjeriTrace2 metodama 
koje cemo objasniti u nastavku. Njihove ThreadStart delegate spremamo u polje, 
te pri startu njihov prioritet postavljamo na ThreadPriority.BelowNormal kako ne 
bi opteretili previse izvodjenje primarnih niti. Nakon toga odspavamo trenutnu 
nit na slucajan broj milisekundi, te cekamo u petlji da sve od novostvorenih 
niti zavrse (napravimo Join()), te ukoliko su nekim cudom jos zive (IsAlive), 
abortiramo ih.

Nakon toga sve to stvaranje i gasenje niti ponavljamo dokle god je program 
aktivan. L00dilo za craxore jerbo je multithreaded programe (pogotovo ove sa
dvoznamenkastim brojem niti :) uzhasno tesko trace-ati normalno, i raumjeti
kako tocno funkcioniraju..

     while ( true ) {
         rng.GetBytes(b);
         int niti_broj = b[0] % 16;
         Thread[] niti = new Thread[niti_broj];
         ThreadStart[] ts = new ThreadStart[niti_broj];
         AntiTrace at = new AntiTrace();

         for ( int p = 0; p < niti_broj; p++ ) {
             ts[p] = new ThreadStart(at.ProvjeriTrace);
             ts[p] += new ThreadStart(at.ProvjeriTrace2);
             niti[p] = new Thread(ts[p]);
             niti[p].Priority = ThreadPriority.BelowNormal;
             niti[p].Start();
         }
         Thread.Sleep(b[0] * 31337 % 10000);
         for ( int p = 0; p < niti_broj; p++ ) {
             niti[p].Join();
             if ( niti[p].IsAlive == true ) {
                 niti[p].Abort();
             }
         }
     }
 }

Sto se AntiTrace klase i dviju metoda unutar njih (ProvjeriTrace() i 
ProvjeriTrace2()), one koriste 2 asm snippeta: jedan koji radi AntiProcDump (ovo 
u slucaju da craxor rucno podesi ovo polje :) i jedan koji provjerava za trace 
koda (GetTracedStatus). Ovaj prvi je vec objasnjen, pogledajmo sad kako radi 
GetTracedStatus (antitrace.asm u izvornom kodu):

"$TRAP_TEST_START$" je marker za HAXOR tool kojim dobivam dump dijela koda 
oznacenog pocetnim i zavrsnim markerom kao CPP-style BYTE niz (ima opcije i za 
pascal, asm.. imam i ja svoj alatich za C#, ali otom potom :)

start:               jmp           __1
                     db            '$TRAP_TEST_START$'

Nakon toga izracunavamo delta-offset - sjetite se, ovo ce se izvoditi JIT-ano 
negdje na hrpi, vjerojatno na drugacijoj lokaciji u memoriji svaki put kad je 
pokrenuto.. zato trebamo koristiti delta-offset za svako apsolutno 
referenciranje! 

__1:                 pusha       
                     call          $+5
trap_delta:          pop           ebx

_tp                  equ           <-trap_delta[ebx]>


Sad izracunavamo offset varijable _traced koja je dio koda, te je @ runtime 
patchamo u kodu gdje se ona koristi koristeci trik sa modifikacijom LC-a. Nakon 
toga pozivamo dio koda __trap koji postavlja trap flag unutar SEH-a, te kao 
prvi SEH handler postavlja dio koda ispod poziva __trap labele. Nakon toga se
postavlja TF (bit 8) u EFLAGS registru, sto ce uzrokovati iznimku 
EXCEPTION_SINGLE_STEP koju ce postavljen SEH handler uhvatiti te inkrementirati 
varijablu _traced. E sad, postavljanje TF ce sjebati manje-vise sve userland 
debuggere (probajte u ollyu trace-ati ovaj snippet koda :), dok ce ga kernel-
mode (sice, syser) jednostavno ignorirati, tj. EH se uopce nece pozivati vec ce 
kod jednostavno nastaviti se izvoditi dalje, kao da se nije nista dogodilo. Nakon 
toga jednostavno vracamo stari EH, pop-amo nazad sve registre da ne bismo nesto 
sjebali te vracamo sadrzaj varijable _traced kao rezultat fje u eax. Tj. ovaj 
snippet koda vraca TRUE ukoliko je trace-an, FALSE ukoliko nije.

			pusha
			lea	       eax, _traced _tp
			mov	       _traced_off _tp, eax
			xor	       eax, eax
			call	       __trap

			mov	       eax, 12345678h 
_traced_off		equ	       $-4
			inc	       byte ptr [eax]
			xor	       eax, eax
			retn

__trap:	       push	       dword ptr fs:[eax]
			mov	       fs:[eax], esp

			pushf
			or	       byte ptr [esp+1], 1
			popf
			nop

			xor	       eax, eax
			pop           dword ptr fs:[eax]
			pop           ebx
			
; poravnaj stog ukoliko je doslo do iznimke, inace makni povratnu adresu

			popa
                     and           [esp.Pushad_eax], 0
			dec	       (_traced _tp).byte ptr 0
			js	       _traceani_smo
                     
                     popa
                     retn
                     
_traceani_smo:       inc           [esp.Pushad_eax]
                     popa
                     retn

_traced              db            0

Sad kad ovo znamo, pogledajmo sto se krije u ProvjeriTrace() metodi:

 unsafe public void ProvjeriTrace()
 {
     fixed ( byte* adresa = AntiProcDump ) {
         Type delegat = typeof(System.Delegate);
         FieldInfo MethodPtr = delegat.GetField("_methodPtrAux", BindingFlags.NonPublic | BindingFlags.Instance);
         DummyDelegat AntiDump = new DummyDelegat(DummyMetoda);
         MethodPtr.SetValue(AntiDump, ( IntPtr )adresa);
         AntiDump(0);
     }

U pocetku prvo pozivamo samo AntiProcDump kod, nista posebno.

     lock ( this ) {
         fixed ( byte* adresa = GetTracedStatus ) {
             Type delegat = typeof(System.Delegate);
             FieldInfo MethodPtr = delegat.GetField("_methodPtrAux", BindingFlags.NonPublic | BindingFlags.Instance);
             DummyDelegat GetTraced = new DummyDelegat(DummyMetoda);
             MethodPtr.SetValue(GetTraced, ( IntPtr )adresa);

             for ( uint k = 0; k < 0x1337; k++ ) {
                 if ( GetTraced(k) == 1 )
                     AppDomain.Unload(AppDomain.CurrentDomain);
             }
         }
     }
 }

Nakon toga radimo lock nad cijelom metodom. E sad, buduci da koristimo gomilu 
niti koje dijele dosta koda (prije svega mislim na onaj embedded asm kod, tj. 
delegati koji pokazuju na isti kod u memoriji!), bilo bi lijepo da ih lockamo. 
Mada ja to bas i ne radim :) Ali lijepo je recimo za crackera da vidi kako ce 
JIT-er stvoriti "lock cmpxchg" instrukcije na pocetku i kraju bloka koji je 
lockan, a koje izmjenjuju/stavljaju ID trenutne niti u SyncBlock indeks za lock-
ani blok koda. U slucaju detekcije trace-anja unloadamo svoj AppDomain (tj. 
gasimo svoj proces u win32 nomenklaturi :)

ProvjeriTrace2() fja takodjer ne sadrzi nista novo, vec samo sluzi kao ispomoc 
jer konstantno provjerava attachanje managed debuggera te u petlji poziva kod za 
detekciju trace-anja. Na taj nacin ce cracker, ukoliko zeli sprijeciti samo-
gasenje programa, trebati bpx-ati i GetCurrentProcess().Kill() koji poziva 
TerminateProcess() te dio za unloadanje trenutnog AppDomain koji je totalno 
managed, te se ne moze na jednostavan nacin bpx-ati. Uglavnom, puno zabave za 
craxore :)

 unsafe public void ProvjeriTrace2()
 {

#if !TESTING
     if (System.Diagnostics.Debugger.IsAttached == true )
         Process.GetCurrentProcess().Kill();
#endif
     byte[] heh = new byte[GetTracedStatus.Length];
     for ( int j = 0; j < heh.Length; j++ )
         heh[j] = GetTracedStatus[j];

     fixed ( byte* adresa = heh ) {
         Type delegat = typeof(System.Delegate);
         FieldInfo MethodPtr = delegat.GetField("_methodPtrAux", BindingFlags.NonPublic | BindingFlags.Instance);
         DummyDelegat GetTraced = new DummyDelegat(DummyMetoda);
         MethodPtr.SetValue(GetTraced, ( IntPtr )adresa);

         for ( uint i = 0; i < 0x1337; i++ ) {
             if ( GetTraced(i) == 1 )
                 Process.GetCurrentProcess().Kill();
         }
     }
 }

Vratimo se sad na glavnu nit programa. Nakon toga se poziva ogromni blok koda u 
BrainfuckNaClipboardu polju kojeg cemo nasnije detaljno obradit, te se glavna 
nit uspava na 2s koliko je (u slucaju da imate Pentium 233) potrebno u nekom
najgorem ludom slucaju asm kodu u njemu da se izvede, te se trenutni proces 
terminira:

    fixed ( byte* adresa = BrainfuckNaClipboardu ) {
         Type delegat = typeof(System.Delegate);
         FieldInfo MethodPtr = delegat.GetField("_methodPtrAux", BindingFlags.NonPublic | BindingFlags.Instance);
         DummyDelegat BrainfuckPhun = new DummyDelegat(DummyMetoda);
         MethodPtr.SetValue(BrainfuckPhun, ( IntPtr )adresa);
         BrainfuckPhun(0);
     }

     Thread.Sleep(2000);    // 2s za obradu clipboarda, nakon toga se gasimo...
     Process.GetCurrentProcess().Kill();
     while ( true ) { } ;
 }

U slucaju da je cracker lukav pa stavi da TerminateProcess() se jednostavno vraca 
(tj. ne radi nista), dodao sam jednu zabavnu beskonacnu petlju koja ce ga jako 
obradovati :) Iskreno ce mu biti JEBENO razluciti je li se proces killa sam zbog
detekcije nekog anti-alata, ili zbog loseg inputa..

///////////////////////////////////////////////////////////////////////////
--[ asm i Nt*
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\


Treci sloj zastite cini sam asm kod spremljen u BrainfuckNaClipboardu polju, te 
rl.asm datoteka u prilozenom izvornom kodu. U biti se u toj jednoj 
datoteci nalaze i stage 3 i stage 4 slojevi zastite.

Krenimo redom analizirati sadrzaj te datoteke.

Prije pocetka je vazno napomenuti 2 cinjenice

1) Ne ocekuje se izlazak nakon pozivanja ovog koda. Ovaj fja poziva gomilu 
API-ja te nije bez razloga stavljena na sam kraj Program.cs. Rezultat ove fje 
moze biti zastopanje programa (prije nego se sam ugasi nakon defaultnog timeouta 
od 2 s), moze biti BSOD sa statusnim kodom 0xbadc0de koji inicira pogresan 
input, te moze biti dobar BSOD cija ce lista statusnog koda i parametara biti
kombinacija ASCII kodova stringa: "challenge completed!".

2) Ovaj kod mora biti offset-independent, te sam minimizirao koristenje globanih
varijabli te pokusao lokalno natrpati koliko sam god mogao (zato je main 
definiran kao proc btw, te minimizirati koristenje nenativnih API-ja tj. 
pozivanje load_dll_address procedure te minimizirati uporabu iz istih razloga 
makroa @get_delta.

Krenimo ispocetka od samog koda unutar proca main:

       @build_UNICODE_STRING <\Device\PhysicalMemory>

@build_UNICODE_STRING je makro koji sam napisao jer me je jako izivciralo 
rucno inicijaliziranje UNICODE_STRING strukture te pozivanje 
RtlInitUnicodeString() API-ja (btw evo moje verzije tog API-ja koji se jako 
cesto koristi za inicijaliziranje te strukture:

; rippano iz ntdll.dll i malchice optimizirano :>
; Argumenti:
;     DestinationString - ptr na UNICODE_STRING strukturu koju inicijaliziramo
;     SourceString      - ptr na UNICODE string kojim inicijaliziramo

RtlInitUnicodeString        proc          c      uses edi
              
                     arg           DestinationString
                     arg           SourceString 
       
       		mov	       edi, SourceString
       		mov	       edx, DestinationString
       		and	       dword ptr [edx], 0
       		mov	       [edx.UNICODE_STR_Buffer], edi
       		test	       edi, edi
       		jz	       __exit
       		
       		or	       ecx, 0FFFFFFFFh
       		xor	       eax, eax
       		repnz         scasw
       		not	       ecx
       		shl	       ecx, 1
       		cmp	       ecx, 0FFFEh
       		jbe	       __1
       		mov	       ecx, 0FFFEh
       
__1:   		mov	       [edx+2], cx
       		dec	       ecx
       		dec	       ecx
       		mov	       [edx], cx

__exit:              ret          ; ne zaboravi generirati jebeni epilog
RtlInitUnicodeString        endp

Svi nativni API-ji koriste UNICODE verzije stringova, tj. cijeli NT kernel je 
interno unicode-baziran kad se dodje do imena resursa i stringova. Prakticno 
cijeli win32 API jest transformacija Ansi verzija u Wide + malo provjera 
validnosti parametara & housekeeping nekih cacheiranih win32 struktura + poziv 
nativnih API-ja. Zato se naviknite na jebene UNICODE_STRING ASAP.

UNICODE_STRING       struc
       UNICODE_STR_Length                 dw            ?
       UNICODE_STR_MaximumLength          dw            ?
       UNICODE_STR_Buffer                 dd            ?
UNICODE_STRING       ends

A sam moj makro radi sljedece:

; o: eax   = ptr na UNICODE_STRING
;    eax+4 = ptr na raw unicode string

@build_UNICODE_STRING   macro         p1, p2
              local  @l, @m, @n, @p
              
              errifnb <p2> "Too much arguments in macro @build_unicode_str."
              
              push          edi
              
              call          @l
@m:           irpc c, <p1>
                     db "&c", 0h
              endm
@n            equ           $ - @m
@l:           pop           edi
              call          @p

              dw            @n 
              dw            @n + 2
              dd            0

@p:           pop           eax
              mov           [eax+4], edi         ; patchaj offset na raw string
              
              pop           edi
endm

Dakle sto sve radi: prvo provjeravamo da li je kreten pozvao makro sa viskom 
parametara (*UVIJEK* radite kad pisete makroe provjere viska parametara jer 
tasm32 puno stvari nekad zna pretpostaviti, sto zna dovesti do jako ruznih 
bugova), nakon toga spremamo edi te iteriramo po karakterima prvog parametra te
ih dumpamo kao WORD, s tim da prvo ide byte dump karaktera pa onda binarna nula.

E sad imamo raw UNICODE string, na kojeg ptr se nalazi u adi na lokalnoj labeli 
@l, te sadpozivamo @p labelu gdje cemo patchovati u ona 2 WORDA i jedan DWORD 
koji predstavljaju UNICODE_STRING strukturu prvo UNICODE_STR_Buffer polje, dok 
se UNICODE_STR_Length i UNICODE_STR_MaximumLength automatski generiraju od 
strane asemblera.

Ovaj makro ustedi puno zivaca i rucnog brojanja karaketera. Vjerujte mi ili se 
uvjerite na tezi nacin :)

Idemo dalje:

                     push          eax			

                     lea	       edi, arg_start
			lea	       ecx, arg_end
			sub	       ecx, edi
			xor	       eax, eax
			cld
			rep	       stosb

Spremamo eax koji je ptr na alociranu strukturu i pri tome obracam paznju na 
jednu jebeno vaznu cinjenicu, a ta je da ta UNICODE_STRING struktura *mora* biti 
jebeno DWORD-aligned inace ce jebeni nativni API-ji koji je koriste vracati (iz 
ntstatus.h gdje mozete vidjeti definicije i ostalih NTSTATUS i inih statusnih 
kodova):

//
// MessageId: STATUS_DATATYPE_MISALIGNMENT
//
// MessageText:
//
//  {EXCEPTION}
//  Alignment Fault
//  A datatype misalignment was detected in a load or store instruction.
//
#define STATUS_DATATYPE_MISALIGNMENT     ((NTSTATUS)0x80000002L)    // winnt

Kita. Zato pazite gdje cuvate ptre :) Recimo ako stringove ovako pravite u .data 
sekciji onda mozete koristiti "align 4" direktivu direktno u kodu, koja ce to 
uraditi umjesto vas, a sto je i pozeljan nacin. Jedino ako ovako u letu to 
zelite napraviti kao ja :)

Zato trebamo spremiti jebeni eax registar jer da smo taj unicode string 
alocirali nakon gore navedenog snippeta koda, morali bi raditi nop-alignment, 
sto sucka.

Nadalje ako embeddate ovakav kod u C#, morat cete dovadati nop-alignment na 4
na pocetku programa, kako bi on radio kako treba.

Gornji snippet koda (od cijeg opisa ja izgleda bjezim cijelo vrijeme :) 
jednostavno inicijalizira sve lokalne varijable alocirane na stogu na nulu, pri 
cemu se koriste dummy lokalne varijable (koje se ne koriste u biti) arg_start i 
arg_end. To radimo zato jer su nam vecina polja nekih lokalnih struktura (recimo 
OBJECT_ATTRIBUTES) treba biti inicjaliziran na 0 pri koristenju, pa ovako sve 
radimo u letu.
                     pop           edi
                     
                     mov           objattr.OBJETCT_ATTR_Length, OBJECT_ATTRIBUTES_SIZE
                     mov           objattr.OBJETCT_ATTR_Attributes, OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE
                     mov           objattr.OBJETCT_ATTR_ObjectName, edi

Uglavnom popamo ptr na strukturu kasnije u edi te inicjaliziramo jedno callgate 
polje koje cu kasnije objasnit te polja OBJECT_ATTRIBUTES strukture (objattr) 
koju inace radi InitializeObjectAttributes makro u DDK.

Nakon toga gradimo unicode string od imena DLL-a user32.dll:

                     @build_UNICODE_STRING <user32.dll>
                     xchg          esi, eax

Ovaj nam je DLL vazan jerbo se u njemu nalaze API-ji za manipulaciju 
clipboardom, i premda koristimo njihovne nativne verzije iz ntdll.dll, 
moramo laodati i ovaj dll jer on uzrokuje lodanje cijelog GDI subsystema bez 
kojeg ni nativni API-ji nece raditi kako treba? Trebalo mi je sat vremena da 
skuzim da je u tome jebeni problem :)

E sad se bavimo malo nativnim apija i njihovim koristenjem u asm-u. Od winXP se 
syscalli koji su sadrzani u KeServiceDescriptorTable polju (koje sadrzi u biti 
4 tavlice, te jos jedna shadow tablica koja nije exportana u ntoskrnl.exe) vise 
ne pozivaju preko klasicnog trapgatea (int 2eh/2bh/2ch do winXP), vec preko 
sysenter (sysexit za izlaz) instrukcije koju je Intel prakticki dizajnirao za 
NT: naime sysenter zahtijeva da u ring0 SS = ring0 CS + 8, sysexit zahtijeva da 
je ring3 CS = ring0 CS + 10h. Za NT je ring0 CS = 08h, ring0 SS = 10h, 
ring3 CS = 1bh, ring3 SS = 23h, tj. bas kao sto i treba biti :>

Iz Intelove dokumentacije [10]:

The SYSENTER and SYSEXIT instructions do not constitute a call/return pair; 
therefore, the system call "stub" routines executed by user code (typically in 
shared libraries or DLLs) must perform the required register state save to 
create a system call/return pair.

To je bas ovdje i slucaj, jerbo su vecina win32API-ja bas to - stubovi za 
nativni API koji obavljaju odgovarajuce spremanje registara i housekeeping 
stoga.

Pogledajmo sljedece eksporte u ntdll.dll:

.text:7C90EB8B ; Exported entry  41. KiFastSystemCall
.text:7C90EB8B
.text:7C90EB8B ; --------------- S U B R O U T I N E ---------------------------
.text:7C90EB8B
.text:7C90EB8B
.text:7C90EB8B ; __stdcall KiFastSystemCall()
.text:7C90EB8B                 public _KiFastSystemCall@0
.text:7C90EB8B _KiFastSystemCall@0 proc near
.text:7C90EB8B                 mov     edx, esp
.text:7C90EB8D                 sysenter
.text:7C90EB8F                 nop
.text:7C90EB90                 nop
.text:7C90EB91                 nop
.text:7C90EB92                 nop
.text:7C90EB93                 nop
.text:7C90EB93 _KiFastSystemCall@0 endp
.text:7C90EB93
.text:7C90EB94 ; Exported entry  42. KiFastSystemCallRet
.text:7C90EB94
.text:7C90EB94 ; --------------- S U B R O U T I N E ---------------------------
.text:7C90EB94
.text:7C90EB94
.text:7C90EB94 ; __stdcall KiFastSystemCallRet()
.text:7C90EB94                 public _KiFastSystemCallRet@0
.text:7C90EB94 _KiFastSystemCallRet@0 proc near
.text:7C90EB94                 retn
.text:7C90EB94 _KiFastSystemCallRet@0 endp
.text:7C90EB94
.text:7C90EB95 ; 
.text:7C90EB95                 lea     esp, [esp+0]
.text:7C90EB9C                 lea     esp, [esp+0]
.text:7C90EBA0                 nop
.text:7C90EBA1                 nop
.text:7C90EBA2                 nop
.text:7C90EBA3                 nop
.text:7C90EBA4                 nop
.text:7C90EBA5 ; Exported entry  43. KiIntSystemCall
.text:7C90EBA5
.text:7C90EBA5 ; --------------- S U B R O U T I N E ---------------------------
.text:7C90EBA5
.text:7C90EBA5
.text:7C90EBA5 ; __stdcall KiIntSystemCall()
.text:7C90EBA5                 public _KiIntSystemCall@0
.text:7C90EBA5 _KiIntSystemCall@0 proc near
.text:7C90EBA5
.text:7C90EBA5 arg_4           = dword ptr  8
.text:7C90EBA5
.text:7C90EBA5                 lea     edx, [esp+arg_4]
.text:7C90EBA9                 int     2Eh             ; DOS 2+ internal - EXECUTE COMMAND
.text:7C90EBA9                                         ; DS:SI -> counted CR-terminated command string
.text:7C90EBAB                 retn
.text:7C90EBAB _KiIntSystemCall@0 endp
.text:7C90EBAB
.text:7C90EBAC ; 




_KiFastSystemCall sadrzi sysenter instrukciju, kao sto i sami vidite, 
_KiFastSystemCallRet je obican near return, dok je _KiIntSystemCall stari nacin 
skakanja trapgatea preko sw interrupta. On se koristi u slucaju da korisnicki
CPU na koji se win XP instalira ne podrzava SYSENTER instrukciju (ima CPUID flag
za testiranje toga), te se u tom slucaju koristi int 2eh. sysenter je znatno 
brzi od interrupta, a buduci da se jaaako puno UM->KM prijelaza dogadjaja 
stalno, kako program poziva nove API-je, razumljiva je MS-ova odluka da koristi 
specijalizirane instrukcije bas za tu svrhu. Na linuxu jos uvijek imate int 80h 
LOLZ ;)

Ovo testiranje preko CPUID se dogadja, naravno, za vrijeme instlacije sistema. 
Tada se odabire pravilan syscall dispatcher i patcha se par DWORD-a tako da svi
API-ji pokazuju na onaj pravi.

U izvornom kodu win [11] proucite \private\ntos\ke\i386\trap.asm ako vas ovo vise 
zanima. Pogotovo obratite paznju na Kss_ExceptionHandler koji se obavlja kad se 
kontrola dobije preko vektora 2EH, te _KiFastCallEntry ciji pocetak ide ovako:

align 16
        PUBLIC _KiFastCallEntry
_KiFastCallEntry        proc
    ;   At entry:
    ;   EAX = service number
    ;   EDX = Pointer to caller's arguments
    ;   ECX = User return address
    ;

    ; Create a stack frame like a call to inner privilege
    ; Load ESP from Tss.Esp0. ints are disabled and esp is not loaded.
ifndef NT_UP
    mov     esp, KGDT_R0_PCR
    mov     fs, sp
    mov     esp, fs:PCR[PcTss]
else
    mov     esp, ss:PCR[PcTss]
endif ;; NT_UP
    mov     esp, ss:[esp].TssEsp0

    push    KGDT_R3_DATA  OR RPL_MASK   ; Push user SS
    push    edx                         ; Push ESP+4
    sub     dword ptr [esp], 4
    pushfd                              ; Push EFlags
    or      dword ptr [esp], EFLAGS_INTERRUPT_MASK
    push    KGDT_R3_CODE OR RPL_MASK    ; Push user CS
    push    ecx                         ; Push Return EIP

ifndef NT_UP
    ; For the MP case, FS is already loaded above
    ENTER_SYSCALL   kfce_a, kfce_t, NoFSLoad
else
    ; set up trap frame and save state
    ENTER_SYSCALL   kfce_a, kfce_t
endif ;; NT_UP

    jmp     _KiSystemServiceRepeat

_KiFastCallEntry endp

Pogotovo obratite paznju na onaj jmo na _KiSystemServiceRepeat jerbo i 
_KiFastCallEntry i Kss_ExceptionHandler zavrsavaju na isti nacin, poanta je bila 
iskoristiti kod koji oba dva koriste, te iskoristiti odgovarajuci (brzhi!) 
mehanizam za ulazak u _KiSystemServiceRepeat.

Da ne ulazim sad u teoriju previse, pogledajmo sto se dogadja kad pozovemo, npr. 
NtClose( HANDLE handle):

.text:7C90D586 ; __stdcall NtClose(x)
.text:7C90D586                 public _NtClose@4
.text:7C90D586 _NtClose@4      proc near  
.text:7C90D586                        
.text:7C90D586                 mov     eax, 19h 
.text:7C90D58B                 mov     edx, 7FFE0300h
.text:7C90D590                 call    dword ptr [edx]
.text:7C90D592                 retn    4
.text:7C90D592 _NtClose@4      endp

e sad se vi pitate, sto je to tako magicno na adresi 7FFE0300h. To je stub koji 
poziva sysenter/int 2eh, u slucaju sysenter zahtijeva kao sto je komentirano gore 
u uvodu _KiFastCallEntry:

    ;   EAX = service number
    ;   EDX = Pointer to caller's arguments
    ;   ECX = User return address

a odgovarajuci @sysenter makro koji ga emulira izgleda ovako:

sysenter	macro  syscall, params
	local	@l, @m
	
	errifb <syscall> "Invalid argument in macro @sysetner: Missing syscall number"
       errifb <params> "Invalid argument in macro @sysenter: Missing parameters"
       
       call           @m

; ntdll!KiFastSystemCall

@l:    mov           eax, syscall
       mov           edx, esp
       dw            340Fh                       ; sysenter
       
@m:    call          @l
       add           esp, (params*4) + 4         ; +4 za dummy EIP

endm

Vazno je uociti 2 stavari: 1) mi smo ti koji cistimo svoje parametre sa stoga 
2) syscall broj za svaki Nt* jest razlicit te ga trebamo rucno vaditi iz disasma 
ntdll.dll. On se geneira od strane skripte pri svakom buildu NT kernela, te je 
vjerojatno za isti API razlicit na razlicitim buildovima. Postoji metoda kako @ 
runtime koristiti odgovarajuci syscall broj tako da radi na svim, ali ona 
nazalost nadilazi opseg ovog clanka i ne da mi se o njoj sad pisati :)

dakle, taj makro koristimo na sljedeci nacin:

              @sysenter      ImeNt*Apija, broj_parametara
              
Vratimo se sad na rl.asm. @Ntmalloc makro radi alociranje memorije preko 
NtAllocateVirtualMemory() te sprema odredisnu adresu u registar dan kao 
parametar, 0 u slucaju greske. Inace, nativni API-ji vracaju STATUS_SUCCESS u 
slucaju uspjesnog izvodjenja:

#define STATUS_SUCCESS                          ((NTSTATUS)0x00000000L)

Dakle, ako je u eax bilo sta != 0 doslo je do greske te opis statusnog koda
gledamo ili u ntstatus.h iz DDK, ili ntstatus <statusni kod> komandom u SoftIce.

U kodu se uglavnom koristi @Ntmalloc, ali na jednom primjeru cak se koristi i 
NtAllocateVirtualMemory() da vidite kako se sa njim radi.

@get_delta makro u letu izracunava delta offset te se registar dan kao parametar 
moze koristiti kao delta registar. Nisam uspio sve strpati kao lokalne 
varijable, te je u nekim dijelovima koda cak i nemoguce koristiti lokalne 
varijable koje su definirane u nekoj  drugoj proceduri (main), recimo u SEH 
handleru, pri skoku u ring0, pa se dosta puta delta offset mora ispocetka 
racunati pa sam iskodirao ovaj makro da mi bude lakse.

                     @Ntmalloc     2048, ecx
                     jecxz         kraj

                     @get_delta    TMP_DLTA3, edx
                     mov           (lde_ptr TMP_DLTA3).dword ptr 0, ecx
       
; inicijaliziraj interni 2K buffer od LDE32 (raspakiraj interne zastavice)
                     
                     push          ecx
                     call          disasm_init
                     add           esp, 4            ; trebas poravnati stog!

LDE32 he z0mbiev LDE (Length Disassemble Engine) - engine koji vraca duzinu 
instrukcije na adresi proslijedjenoj kao parametar, te se taj engine koristi u 
running line engine-u kojeg cu kasnije objasniti. Poanta je da on zahtijeva 2K 
interni buffer kojeg alociramo i proslijedjujemo sa disasm_init.

                     mov           eax, dword ptr fs:[30h]
                     mov           eax, [eax+0ch]
                     mov           eax, [eax+1ch]
                     mov           ebx, [eax+8]     ; uzmi bazu od ntdll.dll
                     
                     gethash      <LdrLoadDll>
                     push          hash
                     call          load_dll_address

                     xor           ebx, ebx
                     push          esp
                     push          esi
                     push          ebx
                     push          ebx
                     call          eax
                     pop           eax        ; baza od user32.dll
                     
Nakon toga loadamo user32.dll koristeci ntdll!LdrLoadDll, koja je interna 
Loaderova fja za loadanje DLL-ova. Njenu dokumentaciju mozete naci u 
\private\ntos\dll\ldrsnap.c, a ovdje je koristim samo da jos vise razveselim 
potencijalnog reversera :)

Nakon toga slijedi dio koji je manje vise sam po sebi razumljiv:


                     xor           edx, edx
                     push          edx
                     push          esp
                     push          ebx
                     @sysenter     NtUserOpenClipboard, 2
                     
                     pop           edx
                     test          eax, eax
                     jz            greska
NtUserOpenClipboard() poziva inace OpenClipboard() API, ovo je varijacija sa 
hWndNewOwner parametrom postavljenim na NULL.
                                          
                     push          MAXIMALNA_VELICINA_BF_POLJA+1
                     mov           edx, esp
                     xor           eax, eax                                  ; neka nam sistem dodijeli baznu adresu
                     push          eax
                     mov           ecx, esp
                     
                     push          PAGE_READWRITE                                         ;
                     push          MEM_COMMIT
                     push          edx
                     push          ebx                                       ; ZeroBits, 0 uvijek.
                     push          ecx                                       ; bazna adresa kako nam se dodijeli..
                     push          -1                                        ; trenutni proces
                     @sysenter     NtAllocateVirtualMemory, 6
                     pop           esi                                       ; bazna adresa koju smo dobili
                     pop           ecx                                       ; poravnaj stog

MAXIMALNA_VELICINA_BF_POLJA jest maximalna velicina jednog polja ciju cu namjenu 
kasnije objasniti :) Bas kao i sljedeci dio koda za MAXIMALNA_VELICINA_STDOUT.

                     test          eax, eax
                     jnz           greska
                     push          esi                                       ; za kasnije
                     
                     push          MAXIMALNA_VELICINA_STDOUT+1
                     mov           edx, esp
                     xor           eax, eax                                  ; neka nam sistem dodijeli baznu adresu
                     push          eax
                     mov           ecx, esp
                     
                     push          PAGE_READWRITE
                     push          MEM_COMMIT
                     push          edx
                     push          ebx                                       ; ZeroBits, 0 uvijek.
                     push          ecx                                       ; bazna adresa kako nam se dodijeli..
                     push          -1                                        ; trenutni proces
                     @sysenter     NtAllocateVirtualMemory, 6
                     pop           esi                                       ; bazna adresa koju smo dobili
                     pop           ecx                                       ; poravnaj stog

                     test          eax, eax
                     jnz           greska
                     push          esi                                       ; za kasnije

Sad provjeravamo da li se na clipboardu nalaze podaci koji su tekstualnog tipa 
(CF_TEXT). Ovo odgovara IsClipboardFormatAvailable() API-ju. Ukoliko je tekst,
tada inas program locka handle na clipboard sa NtUserGetClipboardData() te 2 
puta pozivamo NtUserCreateLocalMemHandle(). Prvi put prosljedjujemo 0 kao 
velicinu buffera te nam vraca STATUS_BUFFER_TO_SMALL statusni kod, te na
prosljedjeni ptr na handle sadrzi potrebnu velicinu buffera koji moramo
opet rucno alocirat, te ponovno pozvat. Ne pitajte me zasto ovo ovako 
funkcionira, ovako je to implementirano u ntdll.dll :)
                     
                     push          CF_TEXT                                    ; je li na clipboardu tekst?
                     @sysenter     NtUserIsClipboardFormatAvailable, 1
                     xchg          eax, ecx
                     jecxz         zatvori_clip  

                     push          ebx
                     push          ebx
                     push          ebx
                     push          esp                                             ; ptr na neku gubavu strukturu od 3 DWORD-a
                     push          CF_TEXT
                     @sysenter     NtUserGetClipboardData, 2                       ; uzmi handle na clipboard
                     test          eax, eax
                     jz            zatvori_clip                                    ; vraca handle, NULL u slucaju greske
                     add           esp, 4*3                                        ; pocisti stog, vraca tip koji *jest* dostupan
                     
                     mov           edi, eax

                     ; push eax
                     ; call user32!CreateLocalMemHandle
                     push          ebx                     
                     push          esp                                              ; 0 ili ako je buffer premal potrebna velicina buffera
                     push          ebx                                              ; velicina alocirane memorije
                     push          ebx                                              ; ptr na alociranu memoriju
                     push          eax                                              ; handle koji je vratio NtUserGetClipboardData
                     @sysenter     NtUserCreateLocalMemHandle, 4                    ; prvi nam put vraca STATUS_BUFFER_TO_SMALL
                     
                     ;pop           edx                                             ; potrebna velicina buffera, mislim da vraca alociranu velicinu
                     mov           edx, esp                                         ; ptr na potrebnu velicinu buffera
                     xor           ecx, ecx
                     push          ecx                                              ; neka nam sistem dodijeli baznu adresu
                     mov           ecx, esp

                     push          PAGE_READWRITE
                     push          MEM_COMMIT
                     push          edx
                     push          ebx                                              ; ZeroBits, 0 uvijek.
                     push          ecx                                              ; bazna adresa kako nam se dodijeli..
                     push          -1                                               ; trenutni proces
                     @sysenter     NtAllocateVirtualMemory, 6
                     
                     pop           esi                                              ; bazna adresa koju smo dobili
                     pop           ecx                                              ; poravnaj stog! neka na stogu ostane velicina alocirane memorije

                     test          eax, eax
                     jnz           greska
                     
                     push          ebx
                     push          ecx
                     push          esi                                       ;       esi = ptr na clipboard buffer
                     push          edi
                     @sysenter     NtUserCreateLocalMemHandle, 4
                     
E sad izracunavamo delta handle u ecx te spremamo dobivene alocirane adrese
na fixne memorijske lokacije.
              
                     @get_delta    TMP_DLTA1, ecx

                     pop           (bf_sim_stdout_polje TMP_DLTA1).dword ptr 0
                     pop           (bf_sim_polje TMP_DLTA1).dword ptr 0
                     mov           (bf_sim_clip TMP_DLTA1).dword ptr 0, esi

Sad bih mogao objasniti kako je crackme zamisljen i koja je svrha ovih 
varijabli. Naime, zadatak je reversera skuziti sta program radi i kako prima 
input, te mu proslijediti odgovarajuci input. Metoda proslijedjivanja je,
ocito, clipboard, a tip podataka jest neki tekst. Ja sam zamislio da taj tekst
bude program u brainfucku [12] koji radi nesto specificno.

E sad se vi pitate koji je kurac brainfuck i zasto sam njega odabrao :)


///////////////////////////////////////////////////////////////////////////
--[ Brainfuck
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

Brainfuck je prije svega jedan bolestan jezik kojeg je izmislio Urban Mller,
kojemu je cilj bio napraviti jezik za Amiga OS 2.0 za kojeg ce napisati 
kompajler ispod 260 bajtova. Na kraju je i uspio :)

Brainfuck program ima jedan glavni "pokazivac", koji se slobodno krece u polju
velicine 30000B, ciji su elementi inicijalno postavljeni na 0. Na pocetku je taj 
pokazivac inicijaliziran da pokazuje na pocetak tog niza.

Sam jezik se sastoji od 8 komandi koje su predstavljene jednim karakterom:

>  Inkrementiraj pokazivac.
<  Dekrementiraj pokazivac.
+  Inkrementiraj bajt nad pokazivacem.
-  Dekrementiraj bajt nad pokazivacem.
.  Ispisi bajt nad pokazivacem (ASCII reprezenzaciju).
,  Ucitaj jedan bajt i spremi na ga lokaciju na koju pokazicac pokazuje.
[  Idi naprijed ispred odgovarajuceg ] ukoliko je bajt na koji pokazivac 
   pokazuje nula.
]  Idi nazad do odgovarajuceg [ osim ako je bajt na koji pokazivac pokazuje 
   nula.

Semantika brainfuck komandi se moze vrlo ekspresivno iskazati u C-like sintaksi, 
pod pretpostavkom da je pokazivac p deklariran kao char* :

>  postaje  ++p; 
<  postaje  --p; 
+  postaje  ++*p; 
-  postaje  --*p; 
.  postaje  putchar(*p); 
,  postaje  *p = getchar(); 
[  postaje  while (*p) { 
]  postaje  } 


Na netu imate gomilu primjera kompajlera i kodova u brainfucku..pokusajte malo 
kodirati u njemu, jako je cudan osjecaj nakon nekog vremena :)

Napisao sam u asmu dvije procedure: brainfuck_provjeri_sintaksu i 
brainfuck_simuliraj. brainfuck_provjeri_sintaksu provjerava sintaksu danog bloka 
memorije, tj. da li taj blok sadrzi validne brainfuck komande (NULL-terminiran).

U ovom su mom specijalnom slucaju dozvoljene sve komande osim ",", tj. 
ucitavanja znaka, jerbo nema smisla da crackme sad ucitava znak. Poanta ja inace 
crackmeja napisati brainfuck program cijim ce se izvodjenjem "ispisati" 
specifican string. Koji? vidjet cete kasnije..

Evo kako sam ja implementirao tu proceduru:

Argumenti:
       p -> ptr na polje u memoriji koje sadrzi potencijalni brainfuck program,
            vraca 1 u slucaju da je validan, 0 inace.

brainfuck_provjeri_sintaksu        proc   pascal      uses ebx

                     arg           p
   		
       		mov	       ecx, p
       		mov	       eax, ecx
       		jmp	       __petljaj

; validni su znakovi sve standardne brainfuck komande osim ","

__loop:	       movsx	       edx, byte ptr [eax]
       		cmp	       edx, '+'
       		jz	       __ok_znak
       		cmp	       edx, '-'
       		jz	       __ok_znak
       		cmp	       edx, '<'
       		jz	       __ok_znak
       		cmp	       edx, '>'
       		jz	       __ok_znak
       		cmp	       edx, '['
       		jz	       __ok_znak
       		cmp	       edx, ']'
       		jz	       __ok_znak
       		cmp	       edx, '.'
       		jz	       __ok_znak
       		xor	       eax, eax
       		jmp	       __izlaz

__ok_znak:	       inc	       eax

; polje je NULL-terminirano

__petljaj:	       cmp	       byte ptr [eax], 0
       		jnz	       __loop
       		
; sad provjeravamo da li u polju ima neuparenih '[' i ']'
       		
       		xor	       ebx, ebx                                  
       		mov	       eax, ecx
       		jmp	       __1

__zagrade:	       movsx	       ecx, dl
       		cmp	       ecx, '['
       		jnz	       __3
       		inc	       ebx

__3:   	       cmp	       ecx, ']'
       		jnz	       __2
       		dec	       ebx

__2:		       inc	       eax

__1:		       mov	       dl, [eax]
       		test	       dl, dl
       		jnz	       __zagrade
       		test	       ebx, ebx
       		jz	       __ok_izlaz
       		xor	       eax, eax
       		jmp	       __izlaz

__ok_izlaz:	       mov	       eax, 1

__izlaz:	       ret

Dok sam brainfuck simulator ovako implementirao:

Argumenti:
       clip         -> ptr na validan brainfuck program
       polje        -> brainfuck polje velicine 30k bajtova.
       stdout_polje -> polje koje simulira stdout

brainfuck_simuliraj         proc   pascal      uses ebx esi edi

                     arg           clip
                     arg           polje
                     arg           stdout_polje
              
                     local         len
                     local         bf_stdout
              
         		xor	       eax, eax
    
       		mov	       edx, clip
       		mov	       len, eax
       		xor	       eax, eax
       		jmp	       __tmp

__1:		       inc	       len
		       inc	       eax

__tmp:		       cmp	       eax, MAXIMALNA_VELICINA_PROGRAMA
       		jge	       __chk_len
       		cmp	       byte ptr [edx+eax], 0
       		jnz	       __1

__chk_len:	       cmp	       len, MAXIMALNA_VELICINA_PROGRAMA-1
       		jnz	       __len_ok
       		xor	       eax, eax
       		jmp	       __izlaz

__len_ok:	       mov	       ecx, stdout_polje
       		xor	       esi, esi
       		mov	       bf_stdout, ecx
       		xor	       ecx, ecx
       		xor	       eax, eax
       		cmp	       eax, len
       		jge	       __izlaz_OK

__loop:	       movsx	       ebx, byte ptr [edx+eax]
       		cmp	       ebx, '+'
       		jnz	       __2
       		mov	       ebx, polje
       		inc	       byte ptr [ebx+esi]
       		jmp	       __provjera

__2:		       movsx	       ebx, byte ptr [edx+eax]
       		cmp	       ebx, '-'
       		jnz	       __3
       		mov	       ebx, polje
       		dec	       byte ptr [ebx+esi]
       		jmp	       __provjera

__3:		       movsx	       ebx, byte ptr [edx+eax]
       		cmp	       ebx, '>'
       		jnz	       __4
       		inc	       esi
       		jmp	       __provjera

__4:		       cmp	       ebx, '<'
       		jnz	       __5
       		dec	       esi
       		jmp	       __provjera

__5:		       cmp	       ebx, '['
       		jnz	       __6
       		mov	       ebx, polje
       		cmp	       byte ptr [ebx+esi], 0
       		jnz	       __provjera
       		inc	       eax
       		jmp	       __loopie

__pici_naprid:
                     movsx	       ebx, byte ptr [edx+eax]
       		cmp	       ebx, '['
       		jnz	        __shittie
       		inc	       ecx

__shittie:    	cmp	       ebx, ']'
       		jnz	        __y
       		dec	       ecx

__y:    	       inc	       eax

__loopie:     	test	       ecx, ecx
       		jg	       __pici_naprid
       		movsx	       ebx, byte ptr [edx+eax]
       		cmp	       ebx, ']'
       		jnz	       __pici_naprid
       		jmp	       __provjera

__6:   		movsx	       ebx, byte ptr [edx+eax]
       		cmp	       ebx, ']'
       		jnz	       __7
       		dec	       eax
       		jmp	       __loopie2


__naprid_jos: 	movsx	       ebx, byte ptr [edx+eax]
       		cmp	       ebx, ']'
       		jnz	       __z
       		inc	       ecx

__z:	       	cmp	       ebx, '['
		       jnz	       __temp
		       dec	       ecx

__temp:	       dec	       eax

__loopie2:	       test	       ecx, ecx
       		jg	       __naprid_jos
       		movsx	       ebx, byte ptr [edx+eax]
       		cmp	       ebx, '['
       		jnz	       __naprid_jos
       		dec	       eax
       		jmp	       __provjera

__7:		       movsx	       ebx, byte ptr [edx+eax]
       		cmp	       ebx, '.'
       		jnz	       __provjera
       		mov	       ebx, polje
       		mov	       edi, bf_stdout
       		mov	       bl, [ebx+esi]
       		mov	       [edi], bl
       		inc	       bf_stdout

__provjera:	       inc	       eax
       		cmp	       eax, len
       		jl	       __loop

__izlaz_OK:	       mov	       eax, 1

__izlaz:	       ret
		
brainfuck_simuliraj endp

Da bude lakse za skuziti sta ovi potprogrami rade, napisao sam (cisto za 
vjezbu :) i njihovu verziju u C-u. Dakle evo brainfuck simulator cijeli:

------------------------------ C O D E ------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>

#define BF_DEBUG

#define MAXIMALNA_VELICINA_PROGRAMA                1024
#define MAXIMALNA_VELICINA_BF_POLJA                30000-1
#define MAXIMALNA_VELICINA_STDOUT                  500-1

int brainfuck_provjeri_sintaksu(char* p)
{
	for( char* clip = p; *clip; clip++) {
		if ( !(*clip == '+' || *clip == '-' || *clip == '<' || *clip == '>' || *clip == '[' || *clip == ']' || *clip == '.') )
			return FALSE;
	}

	// sad provjeravamo da li u polju ima neuparenih '[' i ']'

	unsigned int br = 0;

	for (char* clip = p; *clip; clip++) {
		if( *clip == '[' ) br++;
		if( *clip == ']' ) br--;
	}

	if (br != 0) {
		fprintf(stderr, "Neuparene zagrade u kodu!\n");
		return FALSE;
	}
	return TRUE;
}

int brainfuck_simuliraj(char* clip)
{
	char* polje = (char*) malloc (sizeof(char) * MAXIMALNA_VELICINA_BF_POLJA + 1);
	memset(polje, 0, MAXIMALNA_VELICINA_BF_POLJA+1);

	char* stdout_polje = (char*) malloc(sizeof(char) * MAXIMALNA_VELICINA_STDOUT +1);
	memset(stdout_polje, 0, MAXIMALNA_VELICINA_STDOUT +1);

	int len = 0;

	for ( int i = 0; i < MAXIMALNA_VELICINA_PROGRAMA && clip[i] != 0; i++ )
		len++;

	if ( len == MAXIMALNA_VELICINA_PROGRAMA-1 ) {
		fprintf(stderr, "Prevelik ulazni program!");
			return FALSE;
	}

	char* bf_stdout = stdout_polje;

	int br = 0;
	int l = 0;

	for ( int i = 0; i < len; i++) {
		if ( clip[i] == '+' ) polje[br]++;

		else if ( clip[i] == '-' ) polje[br]--;

		else if ( clip[i] == '>' ) br++;

		else if ( clip[i] == '<' ) br--;

		else if ( clip[i] == '[' ) {
			if ( polje[br] == 0 ) {
				i++;
				while ( l > 0 || clip[i] != ']' ) {
					if ( clip[i] == '[' ) l++;
					if ( clip[i] == ']' ) l--;
					i++;
				}
			}
		}

		else if ( clip[i] == ']' ) {
			i--;
			while ( l > 0 || clip[i] != '[' ) {
				if ( clip[i] == ']' ) l++;
				if ( clip[i] == '[' ) l--;
				i--;
			}
			i--;
		}
		else if ( clip[i] == '.' )	*bf_stdout++ = polje[br];
	}

	fprintf(stdout, "Simulacija zavrsena, stanje output buffera: \"%s\"\n", stdout_polje);
	free (stdout_polje);
	free (polje);
	return TRUE;
}

int main(int argc, char *argv[])
{
	OpenClipboard(NULL);

	if ( IsClipboardFormatAvailable(CF_TEXT) == TRUE )
	{
		HANDLE hPodaci = GetClipboardData(CF_TEXT);
		if ( hPodaci == NULL ) return 1;

		LPVOID podaci = GlobalLock(hPodaci);
		char* clip = (char*) podaci;

		if (brainfuck_provjeri_sintaksu(clip) == FALSE )
			fprintf(stderr, "Leksicka analiza je pala!\n");
		else {
			fprintf(stdout, "Validna sintaksa, zapocinjem simuliranje!\n");

			if (brainfuck_simuliraj(clip) == TRUE )
				fprintf(stdout, "Simulacija uspjseno zavrsena!\n");
			else
				fprintf(stdout, "Simulacija nesupjela :(\n");

			GlobalUnlock(hPodaci);
			return 0;
		}
	}
	else	fprintf(stderr, "Greska: nemam nista na clipboardu!");

	CloseClipboard();
	return 0;
}

------------------------------ C O D E  E N D -----------------------------

Ne da mi se sad komentirat sta kod radi, pokusajte sami skuzit, nije to kvantna 
fizika..

E sad, u vezi ove 2 procedure. Svaka je zasticena za posebnom zastitom i to je u 
biti car slojevitosti ovog crackmeja. Zapocnimo sa brainfuck_provjeri_sintaksu. 
Naime, ona je zasticena sa jednim old skul cudom sto se zove running line 
enkripcija. Ako vidite od cega se sastoji procedura unutar koda, vidjet cete da 
je to uglavnom hex dump + 2 markera i mini stub:


brainfuck_provjeri_sintaksu        proc   pascal      uses ebx

                     arg           p
                     
                     call          udji_ss
                     
                     dd            RL_START_MARKER

db 0a8h, 06eh, 02bh, 0a8h, 0e2h, 0c8h, 008h, 02ch
db 09dh, 033h, 0a0h, 0d9h, 008h, 057h, 001h, 0a0h
db 0d9h, 00eh, 057h, 03eh, 0a0h, 0d9h, 01fh, 057h
db 03bh, 0a0h, 0d9h, 01dh, 057h, 030h, 0a0h, 0d9h
db 078h, 057h, 02dh, 0a0h, 0d9h, 07eh, 057h, 02ah
db 0a0h, 0d9h, 00dh, 057h, 027h, 010h, 0e3h, 0c8h
db 010h, 063h, 0a3h, 01bh, 023h, 056h, 0f3h, 010h
db 0f8h, 0a8h, 0e2h, 0c8h, 033h, 02ch, 09dh, 0e9h
db 0a0h, 0dah, 078h, 056h, 022h, 060h, 0a0h, 0dah
db 07eh, 056h, 022h, 068h, 063h, 0a9h, 033h, 0a7h
db 0f1h, 056h, 0c9h, 0a6h, 0f8h, 057h, 027h, 010h
db 0e3h, 0c8h, 02ah, 09bh, 022h, 023h, 023h, 023h

                     dd            RL_MARKER
              
	              ret
		
brainfuck_provjeri_sintaksu endp

E sad se vi s pravam pitate koji je to jebeni kurac :)


///////////////////////////////////////////////////////////////////////////
--[ Running-line
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

Running line je tehnika koju je izmislio Serge Pachkovsky [13] te je 
provotno prakticirana na MS-DOS-u. Ta se metoda sastoji od hookiranja vektora za 
int 1, postavljanja TF nakon svake instrukcije, te dekriptiranja svake 
instrukcije *neposredno prije* se ona izvodi, de kriptiranje nazad *nakon* sto 
se ona izvede.

Ocito, na win iz ring3 ne mozemo bas prckati po IDT te postavljati svoj int 1 
handler (mogli bismo iz kernel-mode, ali o tom drugom prilikom..), ali zato u tu 
svrhu mozemo iskoristiti SEH.

Prvo proucimo udji_ss proceduru:

udji_ss              proc          near
                     pop           eax
                     pushf
                     or            byte ptr [esp+1], 1
                     popf
                     jmp eax
udji_ss              endp

TF je bit 8 koji postavljamo i pop-amo u EFLAGS. Prva instrukcija (onaj jmp eax) 
se ne trace-a), te se iznimka tipa EXCEPTION_SINGLE_STEP obradjuje u defaultnom
exception handleru.

@SEH_SetupRL je varijacija poznatog @SEH_SetupFrame makroa koja bas sluzi za 
svrhu running line i ovako sam je napisao:

@SEH_SetupRL      	macro	p1, p2, p3, p4, p5, p6, p7, p8, p9
	              local set_new_eh

                     call set_new_eh
                     irp param, <<&p1>, <&p2>, <&p3>, <&p4>, <&p5>, <&p6>, <&p7>, <&p8>, <&p9>>
		              ifb <param>
			              exitm
		              endif
		              param                          ; za running line prvo trebamo raditi dekodiranje instrukcija!
                     endm
                     xor           eax, eax                ; EXCEPTION_CONTINUE_SEARCH = 0
                     retn
                     
set_new_eh:          xor           edx, edx
	              push          dword ptr fs:[edx]
	              mov           dword ptr fs:[edx],esp
endm

Jedina je razlika u tome sto se na radi 

mov     esp,[esp.EH_EstablisherFrame]

vec direktno izlista parametre (tj. instrukcije koje se izvode, obicno samo 
jedna..) te onaj

                     xor           eax, eax      ; EXCEPTION_CONTINUE_SEARCH = 0
                     retn

koji nam je potreban da bi se iznimke mogle dalje konzekutivno procesirati. Za 
vise info-a o SEH-u, ukoliko ne znate sto je to, preporucam fenomelan post na 
newsima Anatolya Vorobeya [18].

Zato prije poziva brainfuck_provjeri_sintaksu postavljamo SEH na sljedeci nacin:

                     @SEH_SetupRL   <call dekodiraj_seh>

dekodiraj_seh je, pogadjate, running-line engine. Pogledajmo kako on 
funkcionira.

dekodiraj_seh        proc          c

                     arg           eh:Except_Handler

Svaki EH kao arguement prima Except_Handler strukturu koja je ovako definirana 
(myapis.inc):

Except_Handler                  STRUC
        EH_Dummy                DD      ?                    ; Ret address
        EH_ExceptionRecord      DD      EXCEPTION_RECORD PTR ?
        EH_EstablisherFrame     DD      BYTE PTR ?
        EH_ContextRecord        DD      CONTEXT PTR ?
        EH_DispatcherContext    DD      BYTE PTR ?
Except_Handler                  ENDS

EH_ExceptionRecord polje sadrzi EXCEPTION_RECORD strukturu u cijem polju 
ER_ExceptionCode mozemo vidjeti razlog zbog kojeg se iznimka dogodila (lose 
dereferenciranje ptra, dijeljenje s nulom, postavljen trap flag :))

E sad nas bas zanima ta iznimka kad je trap flag postavljen. Ona inace ima 
statusni kod EXCEPTION_SINGLE_STEP. Dakle, mi mozemo, nakon sto se instrukcija 
izvede, uciniti da tok izvodjenja programa skoci na nas EH, te imati pristup 
registririma u kontekstu toka programa u kojem se iznimka dogodila, preko 
EH_ContextRecord polja.

CONTEXT struktura sadrzi "kontekst" niti, trenutno stanje registara u njoj. Pri 
suspendiranju niti to se stanje registara sprema, a pri nastavku izvrsavanja, 
kopira se nazad u sve registre. Pogledajte npr. uporabu Set/GetThreadContext() 
API-ja za detalje.

Mi unutar EH-a mozemo dakle kontrolirati *sve* registre running line koda koji 
se izvrsava. Ukljucujuci i EIP :) Dakle, me mozemo efektivno natjerati kod da 
skoci na proizvoljnu lokaciju, mozemo raditi sto god hocemo sa memorijom koju 
program koristi i koja se nalazi u njegovom adresnom prostoru.

Na slican nacin mi bas to i radimo. Vec sam spomenuo da dekriptiramo i 
enkriptiramo po jednu instrukciju kako se izvodi. E sad, problem je kako 
odrediti duzinu instrukcije. Recimo deroko je u jednom papiru koji se takodjer 
nalazi u ovom zine-u to rijesio dodavanjem int 3 nakon svake instrukcije (0CCh 
opkod). Ja koristim malo elegantniji nacin ipak, dekriptiram prvih X bajtova 
gdje je X duljina najvece instrukcije u bloku koda (ovdje sam uzeo 10 tek tako)
te na taj blok koda pozivam LDE32. Nakon toga kriptiram nazad ostatak.

TF se u EFLAGS resetira svaki put nakon iznimke, te ga je potrebno postaviti 
rucno unutar EH-a. Nadalje, potrebno je takodjer odrediti nacin kako znati kad 
treba zavrsiti dekriptiranje, tj. potreban je specijalan marker za kraj (i 
eventualno za pocetak ukoliko zelimo neki inicijalizacijski kod raditi). U mom 
engine-u koristim RL_START_MARKER i RL_END_MARKER za pocetak odnosno kraj bloka 
koda koji se dekriptira.

Nadalje, takodjer su potrebne 3 externe varijable:

lde_ptr              dd            0
preth_instr_len      dd            0
offset_prosla        dd            0

Sjetite se da je cijeli engine smjesten u EH-u, dakle morat ce ponovo sam 
racunat delta offset za pristup registrima jer ce oni svaki put opet biti 
sjebani. lde_ptr je adresa onog privremenog 2K buffera za LDE32 kojeg moramo
proslijedjivati kao parametar, preth_instr_len sadrzi adresu a offset_prosla 
offset prosle instrukcije.

Adresa prosle instrukcije nam je potrebna jer LDE32 ne moze disasmati "unazad",
u biti nam je dosta adresa i onda bismo teoretski mogli pozvati LDE32 opet da 
dobijemo duzinu, ali eto, ja sam to ovako napravio i sta sad :)

Dakle, nakon sto dodjemo do trenutne instrukcije i dekriptiramo tocno njezinu 
duljinu, reenkriptiramo i ostatak od onog 10-bajtnog buffera i jednu prethodnu
instrukciju koja se izvodila.

Dakle sve u svemu, relativno jednostavan koncept, a tako jebeno mocan :) 
Pogledajmo sad implementaciju:

dekodiraj_seh        proc          c

                     arg           eh:Except_Handler

                     pusha

Provjerimo razlog zbog kojeg je EH pozvan. Ako kod iznimke nije 
EXCEPTION_SINGLE_STEP dogodio se bug u samom programu koji se 
emulira, sto nije dobro :/
                     
                     mov           eax, eh.EH_ExceptionRecord
                     cmp           dword ptr [eax.ER_ExceptionCode], EXCEPTION_SINGLE_STEP
                     jnz           __kraj

Sad uporedjujemo da li je *eip == RL_END_MARKER, tj. da li smo
dosli do kraja enkriptiranog koda. Ako da, eip += 4 i makni TF.
                     
                     mov           ebx, eh.EH_ContextRecord
                     mov           edi, [ebx.CONTEXT_Eip]
                     cmp           dword ptr [edi], RL_END_MARKER
                     jz            __RL_END_MARKER

Inace i dalje ostavi TF.

                     or            [ebx.CONTEXT_EFlags], 100h

Provjeri da li je *eip == RL_START_MARKER, tj. da li smo dosli
do pocetka enkriptiranog koda. Ako da, eip +=4 i ostavljamo TF.

                     cmp           dword ptr [edi], RL_START_MARKER
                     jnz           __nije_pocetak

E sad smo prije pocetka koda kojeg trebamo en/dekriptirati
                     
                     add           [ebx.CONTEXT_Eip], 4                             ; preskoci RL_START_MARKER
                     add           edi, 4
 
Dekriptiramo 10 instrukcija, izracunavamo duljinu prve te je spremamo 
u varijablu i reenkriptiramo. U biti mozemo proizvoljan broj, sad
je RL_START_MARKER postavljen na 10. Enkripcija jest naaaajobicnije 
XOR-anje u ovom slucaju, dakle nista komplicirano te reverser moze
skodirati dekripciju za 10s....pod uvjetom da skuzi sta ovaj engine
radi ;) Naravno, nista vas ne sprijevava da ovdje dodate bilo koji,
proizvoljni enkripcijski algoritam, kao ida pozivate neku externu 
fju koja ce raditi *samo* enkripciju/dekripciju proslijedjenog
komadica koda... Mogucnosti je bezbroj, ovo je samo kostur :)

__nije_pocetak:      push          MAX_DEKRIP_LEN
                     pop           ecx
                     
                     xor           edx, edx
__dekrip_lde:        xor           byte ptr [edx+edi], XOR_BAJT
                     inc           edx
                     loop          __dekrip_lde
                     
                     @get_delta    TMP_DLTA4, edx
                     
                     push          edi
                     push          (lde_ptr TMP_DLTA4).dword ptr 0
                     call          disasm_main
                     add           esp, 8
                     
                     inc           eax
                     jz            __error
                     dec           eax                                ; duljina prve instrukcije 
                     
                     mov           esi, (preth_instr_len TMP_DLTA4).dword ptr 0
                     
UVIJEK spremamo offset prosle instrukcije u varijablu zbog mogucih grananja i 
inih picki materina... kao i zbog cinjenice da LDE32 ne moze ici unazad :/

                     push          (offset_prosla TMP_DLTA4).dword ptr 0
                     mov           (offset_prosla TMP_DLTA4).dword ptr 0, edi
                     
                     mov           (preth_instr_len TMP_DLTA4).dword ptr 0, eax 
                     push          MAX_DEKRIP_LEN
                     pop           ecx
                     sub           ecx, eax                           ; MAX_DEKRIP_LEN - (duljina sljedece instrukcije) 
                     add           edi, eax                           ; ostatak nazad kriptiramo
                     xor           edx, edx
                     
__krip_lde:          xor           byte ptr [edx+edi], XOR_BAJT
                     inc           edx
                     loop          __krip_lde
                     
                     pop           edx                                ; edx = vrijednost starog offset_prosla
                     
jos moramo kriptirati i prethodnu instrukciju. u slucaju da je to 
RL_START_MARKER to ne radimo:

                     sub           edi, eax
                     cmp           [edi-4], RL_START_MARKER
                     jz            __kraj
                     
ecx = 0 samo u slucaju prve instr.
                                          
                     mov           ecx, edx
                     jecxz         __nije_prosla_skok
                     mov           edi, ecx
                     jmp           __1

__nije_prosla_skok:  sub           edi, esi
__1:                 mov           ecx, esi                                         ; duljina prethodne instrukcije
                     xor           edx, edx
__krip_ldey:         xor           byte ptr [edx+edi], XOR_BAJT
                     inc           edx
                     loop          __krip_ldey
                     
                     jmp           __kraj
                     
__RL_END_MARKER:     add           [ebx.CONTEXT_Eip], 4                             ; preskoci RL_END_MARKER
                     and           [ebx.CONTEXT_EFlags], 0FFFFFEFFh                 ; makni trap flag
                     jmp           __kraj
       
__error:             jmp           $                                                ; greska, zastopaj se :>
__kraj:              popa
                     xor           eax, eax                                         
                     ret

dekodiraj_seh        endp

Moguce je naravno postaviti int3 unutar SEH-a i gledati kako ovo funkcionira
u stvarnosti..Naravno, moguce je i trace-ati izvorni program, ali samo prvu
instrukciju u syser/sice, jerbo ce oni sjebati TF tako da se nas EH nece 
pozvati, pa trebamo TF rucno postaviti nakon svakog poziva udji_ss.

Sto se tice stvaranja dump-a XOR-iranog nekim kljucem, napravio sam mali C#
programcic koji radi istu stvar sto i HAXOR tool, samo sto jos i XOR-ira sa
danim kljucem svaki bajt:

------------------------------ C O D E ------------------------------------
using System;
using System.IO;
using System.Text;

namespace dumpXOR
{
    class Program
    {

        static void IspisiUporabu()
        {
            Console.WriteLine("dumpXOR <ulazna datoteka> <izlazna datoteka> /S=<start labela> /E=<end labela> /XOR=<XY>");
        }

        static void Main(string[] args)
        {
            if ( args.Length != 5 ) {
                IspisiUporabu();
                return;
            }

            if ( File.Exists(args[0]) == false ) {
                Console.WriteLine("Greska: datoteka {0} ne postoji!", args[1]);
                return;
            }

            if ( args[2].StartsWith("/S=") == false || args[3].StartsWith("/E=") == false || args[4].StartsWith("/XOR=") == false
                 || args[4].Length != 7 )  {
                Console.WriteLine("Greska: pogresna sintaksa!");
                return;
            }

            string xor_kljucs = args[4].Substring(5);

            char prvi_znak = Char.ToUpper(xor_kljucs[0]);
            char drugi_znak = Char.ToUpper(xor_kljucs[1]);

            if ( !( Char.IsNumber(prvi_znak) || prvi_znak == 'A' || prvi_znak == 'B' || prvi_znak == 'C' ||
                 prvi_znak == 'D' || prvi_znak == 'E' || prvi_znak == 'F' ) ||
                 !( Char.IsNumber(drugi_znak) || drugi_znak == 'A' || drugi_znak == 'B' || drugi_znak == 'C' ||
                 drugi_znak == 'D' || drugi_znak == 'E' || drugi_znak == 'F' ) ) {
                Console.WriteLine("Greska: XOR kljuc {0} nije validan hex bajt!", xor_kljucs);
                return;
            }

            byte xor_kljuc = Convert.ToByte(xor_kljucs, 16);

            Console.WriteLine("+Koristim kljuc {0}", xor_kljuc);
            if ( File.Exists(args[1]) == true ) {
                Console.WriteLine("->Upozorenje: brisem vec postojecu odlaznu datoteku {0}", args[1]);
                File.Delete(args[1]);
            }

            using ( BinaryReader ulazni = new BinaryReader (new FileStream(args[0], FileMode.Open, FileAccess.Read) ) ) {
                using ( StreamWriter odlazni = new StreamWriter(new FileStream(args[1], FileMode.CreateNew, FileAccess.Write)) ) {
                    string start_labela = args[2].Substring(3);
                    Console.WriteLine("+Koristim startnu labelu {0}", start_labela);
                    string end_labela = args[3].Substring(3);
                    Console.WriteLine("+Koristim end labelu {0}", end_labela);

                    byte[] buf = new byte[start_labela.Length];
                    Console.WriteLine("+Velicina datoteke {0} bajtova", ulazni.BaseStream.Length);

                    bool nadjena_signatura = false;
                    int start_offset = 0;
                    int end_offset = 0;

                    byte[] buf2 = new byte[end_labela.Length];
                    bool nadjena_signatura2 = false;

                    for ( int i = 0; i < ulazni.BaseStream.Length; i++ ) {
                        ulazni.Read(buf, 0, buf.Length);
                        ulazni.BaseStream.Position = i + 1;
                        if ( Encoding.ASCII.GetString(buf) == start_labela ) {
                            Console.WriteLine("+Pronadjena start signatura na offsetu {0}", i);
                            nadjena_signatura = true;
                            start_offset = i + start_labela.Length;
                        }
                    }

                    if ( nadjena_signatura == false ) {
                        Console.WriteLine("Greska: ne mogu pronaci u datoteci signaturu {0}!", start_labela);
                        return;
                    }
                    
                    else {
                        for ( int i = start_offset; i < ulazni.BaseStream.Length; i++ ) {
                            ulazni.Read(buf2, 0, buf2.Length);
                            ulazni.BaseStream.Position = i + 1;
                            if ( Encoding.ASCII.GetString(buf2) == end_labela ) {
                                Console.WriteLine("+Pronadjena end signatura na offsetu {0}", i);
                                nadjena_signatura2 = true;
                                end_offset = i;
                            }
                        }
                    }

                    if ( nadjena_signatura2 == false ) {
                        Console.WriteLine("Greska: ne mogu pronaci u datoteci signaturu {0}!", end_labela);
                    }

                    odlazni.WriteLine("; generirana datoteka. *ne* editirati\n");
                    odlazni.WriteLine("XOR_BAJT\t\tequ\t\t0{0}h{1}", xor_kljucs, Environment.NewLine);
                    odlazni.WriteLine("dumpXOR:");
                    int j = 0;
                    byte b;
                    ulazni.BaseStream.Position = start_offset;
                    Console.Write("+Zapisujem datoteku....");
                    for ( int i = start_offset; i < end_offset; i++ ) {
                        if ( j++ % 8 == 0 ) odlazni.Write("db ");
                        b = ulazni.ReadByte();
                        byte x = (byte)(b ^ xor_kljuc);
                        odlazni.Write(String.Format("0{0:x2}h", x));
                        if ( j % 8 == 0 ) odlazni.Write(Environment.NewLine);
                        else odlazni.Write(", ");
                    }
                    Console.WriteLine("gotovo.");
                }
            }
        }
    }
}
------------------------------ C O D E  E N D -----------------------------

Program se poziva ovako:

dumpXOR <ulazna datoteka> <izlazna datoteka> /S=<start labela> /E=<end labela> /XOR=<XY>

gdje je XY je hex bajt kojim XOR-iramo kod izmedju start i end labele.

Naravno, u slucaju da promijenite vrstu enkripcije, morat cete ili napraviti
svoj tool ili uraditi nesto trece..

Nakon sto ste tako dump-ali svoj kod, samo dodajte RL_START_MARKER i 
RL_END_MARKER na pocetak odnosno kraj te postavite TF, engine ce sam obaviti 
svoje..

I na taj nacin stitimo provjeru sintakse programa :)

Running line je nesto jaaaako jaaaaako jaaaako kul, vjerujte mi. Mogucnosti 
manipulacije su zaista beskrajne, pogtovo u VX tehnologiji. Recimo primarno 
tijelo virusa mozemo rastaviti pojedincno na instrukcije, te onda svaku zasebno
alocirati u zasbenoj adresi na hrpi te izvoditi koristeci running line engine. 
Tako bismo napravili *dinamcku* obsfukaciju koda, za razliku od klasicnih 
polimorfnih i metamorfnih engine-a, koji su staticke obsfukacije. Na taj bi
nacin efektivno unistili prepoznavanje tijela virusa preko signature.

Kao sto sam vec spomenuo, mjesta za napredak ima dosta, pogotovo u dijelu gdje 
se kod dekriptira/enkriptira. Sigurno je potrebna neka naprednija zastita sa
klizecim kljucem.

Nadalje, potrebno je zastiti i sam running line engine od reversanja, te od 
njegove detekcije od strane AV. Ako smo uspjeli zastiti running line engine, 
nema nikakve jebene sanse de AV detektira tako "rasprseno" tijelo virusa...
pa cak i da nije rasprseno, kroz neki x-raying posrani, ukoliko je enkripcija
dovoljno kompleksna.

Sam engine bi se mogao zastititi, pogodite kako...jos jednim running line 
slojem! Teoretski bi ih mogli ugnijezdjivati koliko god zelimo :> Buduci da je
sam engine relativno mal, moze se strpati u 100-ak instrukcija, mozemo ga jako
opako morfirati sa bilo kojim externim morferom (tipa KME), te ga mozemo 
zastititi dodatnim running line slojevima.

Engine je u cijelosti napisan iz glave, a debuggiranje ovakvog jebenog govneta
koje je tako jebeno otporno na jebene debuggere je jebena nocna mora. Jedino 
info sto sam nasao na netu jest u jednom codebreakers magazinu [14], 
ali autor (The +Q) nazalost prezentira kod koji bi trebao demonstrirati 
izvodjenje koda unazad preko running line, ali njegov kod je bugovit i ne radi 
:>

Navodno postoji i jedan DOS-ovski virus koji se u cijelosti izvodi UNAZAD 
koristeci running line!


///////////////////////////////////////////////////////////////////////////
--[ One ring to bring 'em all...
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

ring0. Savrsentvo. Nema ogranicenja. Nema kontrole. Nema zastite. Sve je 
apsolutno moguce, i nitko nas jebeno ne moze zaustaviti da ne uradimo bilo sta..
Sjebemo EEPROM BIOS-a, sjebemo particijsku tablicu, sakrijemo virus u NTFS bad 
sectorima...sve.

Konacno je postala jebena stvarnost i to bez koristenja jebenih exernih drivera 
:)

Ustvari sad bih vas najradije uputio na odlican clanak crazylorda [15] koji je i 
otkrio ovu metodu. Radi se o sljedecem: Pathcamo \Device\PhysicalMemory tj. 
device koji predstavlja fizicku memoriju (/dev/mem na linuxu, ne /dev/kmem koji 
je virtualna memorija) i to na nacin da odredimo bazu preko sgdt, pretvorimo je 
u fizicku adresu te instaliramo callgate koji pokazuje na nas kod i koji 
pozivamo sa jednim far call-om.

Prije toga trebamo podesiti sigurnosni deksriptor (tj. ACL) od objekta kako 
bismo imali write pristup na \Device\PhysicalMemory objekt, tj. samu fizicku 
memoriju koju preko njega mapiramo.

Zaista mi se ne da sad objasnjavat u detalje teoriju, pogledajte crazylordov 
papir, tamo vam je sve to lijepo objasnjeno. U prilozenom papiru kod je u C-u, 
te je, kako to i prilici nama l33t haxorima :), preveden u asm. Stavio sam
nativne API-je gdje je to god bilo moguce.

Prvo alociramo 2 buffera velicine SYSTEM_MODULE_INFORMATION_SIZE i 
SYSTEM_BASIC_INFORMATION_SIZE koji su strukture koje popunjava 
NtQuerySystemInformation() API.

NtQuerySystemInformation vraca gomilu sistemskih postavki..sa njim je moguce 
saznati neke detalje koje win32 API uopce ni ne pruza..nama su u ovom trenutku
vazne 2 kategorije SystemBasicInformation i koji popunjava lokalnu varijablu 
sys_info i SystemModuleInformation koji koristimo za trazenje baze ntoskrnl.exe.

SYSTEM_BASIC_INFORMATION_SIZE nam je potreban zbog polja 
SYS_BASIC_INFO_AllocationGranularity (obicno bude 10000h) koji se koristi pri 
izracunavanju offseta pri translaciji u fizicku adresu (algo by ElicZ ;).

                     @Ntmalloc     SYSTEM_MODULE_INFORMATION_SIZE, ecx
                     jecxz         kraj
                     mov           modinfo, ecx
                     
                     @Ntmalloc     SYSTEM_BASIC_INFORMATION_SIZE, ecx   
                     jecxz         kraj
                     mov           sys_info, ecx

                     push          0
                     push          SYSTEM_BASIC_INFORMATION_SIZE
                     push          ecx
                     push          SystemBasicInformation
                     @sysenter     NtQuerySystemInformation, 4

Enumerirajmo sistemske module i potrazimo onaj koji je "ntoskrnl.exe". 
MojPathFindFileName je moja, rucna verzija PathFindFileName API-ja, koji vraca
samo filename danog full path-a. syser debugger dosta sjebe stvari u kernelu,
te nekad zna promijeniti ntoskrnl.exe u kernel1.exe, pa ovaj blok koda nece 
raditi kako treba. Zato sam u slucaju debuggiranja sa sysermo stavio da vraca 
fiksnu adresu kernela.

GetNtoskrnl:         push          0                                         ; enumemeriraj module
	              push          SYSTEM_MODULE_INFORMATION_SIZE
	              push          modinfo
	              push          SystemModuleInformation
	              @sysenter     NtQuerySystemInformation, 4
                     
                     mov           esi, modinfo                               ; eax = broj modula
                     lodsd                                                    ; esi = ptr na prvi modul
                     xchg          eax, ebx
                     
__loop:              mov           edi, esi
                     add           edi, SYSMOD_abName
                     
                     push          edi
                     call          MojPathFindFileName

IFDEF SYSER_DEBUGGING
                     ; kad je syser instaliran imamo kernel1.exe!
                     
                     mov           eax, 0804D7000h

                     mov           ntoskrnl, eax
                     jmp           __nasao_ntoskrnl
ENDIF                     
                     or            [eax], 20202020h                    ; pretvori u mala slova, 5. bit postavljen -> malo slovo
                     cmp           [eax], 'sotn'
                     jnz           __2
                     or            [eax+4], 20202020h
                     cmp           [eax+4], 'lnrk'
                     jnz           __2
                     or            [eax+8], 20202020h
                     cmp           [eax+8], 'exe.'
                     jnz           __2
                     
                     mov           eax, [esi.SYSMOD_pAddress]         ; imamo kernel!
                     mov           ntoskrnl, eax
                     jmp           __nasao_ntoskrnl

__2:                 add           esi, SYSTEM_MODULE_SIZE
                     dec           ebx
                     jns           __loop

                     jmp           $                                 ; nismo nasli kernel...major fuckup :/

U slucaju da smo nasli bazu od ntoskrnl.exe, otvaramo handle na inicijalizirani 
objattr:

__nasao_ntoskrnl:    lea           eax, objattr
              	push          eax
              	push          WRITE_DAC + READ_CONTROL
              	lea           eax, mem_section
              	
              	push          eax
              	@sysenter     NtOpenSection, 3 
              	
              	mov           ebx, mem_section

Sad uzimamo sigurnosni deskriptor handle-a i modificiramo ga, jerbo po defaultu 
samo Admininistrator/SYSTEM mogu pisati po \Device\PhysicalMemory, pri cemu 
stvaramo novi ACL sa zeljenim pravima, te apdejtamo sigurnosni deskriptor sa tim 
ACL:

             	       xor           ebx, ebx

                     @get_dll_api  advapi32.dll, GetSecurityInfo
                     
              	lea           ecx, p_sec_descr
              	push          ecx
              	push          ebx
              	lea           ecx, p_old_dacl
              	push          ecx   
              	push          ebx
              	push          ebx
              	push          DACL_SECURITY_INFORMATION
              	push          SE_KERNEL_OBJECT
              	push          mem_section
              	call          eax
	              mov		access.EXPACCESS_grfAccessPermissions, SECTION_ALL_ACCESS
	              inc           ebx
	              mov		access.EXPACCESS_grfAccessMode, ebx                       ; GRANT_ACCESS
	              mov		access.EXPACCESS_Trustee.TRUSTEE_TrusteeForm, ebx         ; TRUSTEE_IS_NAME
	              mov		access.EXPACCESS_Trustee.TRUSTEE_TrusteeType, ebx         ; TRUSTEE_IS_USER
	              dec           ebx

	              @pushsz       "CURRENT_USER"
	              pop		access.EXPACCESS_Trustee.TRUSTEE_ptstrName
              	
                    	@get_dll_api  advapi32.dll, SetEntriesInAclA
                    	xchg          eax, edx
              	
              	lea           ecx, p_new_dacl
              	push          ecx 
              	push          p_old_dacl
              	lodsd
              	push          esi                                       ; offset access
              	push          1
              	call          edx                                       ; stvori novi ACL
                     
                     @get_dll_api  advapi32.dll, SetSecurityInfo
                     
                     push          ebx
                     push          p_new_dacl
                     push          ebx
                     push          ebx
                     push          DACL_SECURITY_INFORMATION              
              	push          SE_KERNEL_OBJECT	
              	push          mem_section
              	call          eax                                       ; apdejtaj sigurnosni deskriptor sa novim ACL
                     
              	push          mem_section
              	@sysenter     NtClose, 1


Sad kad imamo r/w pristup, ponovno otvaramo handle:
              	
              	lea           eax, objattr
              	push          eax
              	push          SECTION_MAP_READ + SECTION_MAP_WRITE
              	lea           eax, mem_section
              	push          eax
              	@sysenter     NtOpenSection, 3

E sad trebamo stvoriti callgate kako spada. U biti mi nas callgate instaliramo
na zapis broj GDT_SELEKTOR, a koji je postavljen na 99 u ovom kodu (trebao bi 
biti slobodan na svim verzijama win). Pravilna bi metoda bila skeniranje svih
zapisa u gdt, te trazenje slobodnog, tj. onog koji nije prisutan (nema 
postavljen P bit (od Present)).

Bazu gdt dobivamo sgdt instrukcijom, a izgled strukture koju ona vraca je u DDK
definirana ovako:

typedef struct _KGDTENTRY {
   WORD LimitLow;           // velicina GDT u bajtovima
   WORD BaseLow;            // adresa GDT (donji dio)
   WORD BaseHigh;           // adresa GDT (gornji dio)
} KGDTENTRY, *PKGDTENTRY;

Pri cemu je dobivena adresa virtualna, te je trebamo prevesti u fizicku, tj.
pozivamo GetPhysicalAddress proceduru sa parametrom BaseHigh:BaseLow.

Sam callgate deskriptor zapis jest definiran sljedecom strukturom:

typedef struct _CALLGATE_DESCRIPTOR {
   USHORT offset_0_15;      // donji dio adrese r0 koda
   USHORT selector;
   UCHAR  param_count :4;
   UCHAR  some_bits   :4;
   UCHAR  type        :4;   // tip segmenta ili gate-a
   UCHAR  app_system  :1;   // segment deskriptor (0) ili sistemski segment (1)
   UCHAR  dpl         :2;   // iz kojeg prstena ga mozemo zvati
   UCHAR  present     :1;
   USHORT offset_16_31;   // gornji dio adrese r0 koda
} CALLGATE_DESCRIPTOR, *PCALLGATE_DESCRIPTOR;

te je u nasem asm kodu ovako preddefiniran varijablom callgatex:

	callgatex		dw	0						; donji dio adrese
				dw	KGDT_R0_CODE					; NT ring0 selektor
				dw	1110110000000000b		              ; misc bitovi (5 najnizih = # parametara)
				dw	0
KGDT_R0_CODE je u DDK definiran kao ring0 selektor na NT-u (08h)

Takodjer mozemo specificirati broj parametara ukoliko ih ring0 procedura uopce 
prima. Vazno je naglasiti da u tom slucaju ona sama mora pocistiti stog prije 
izlaska.

                     @get_delta    TMP_DLTA5, edx
              	lea           eax, ring0_proc TMP_DLTA5

; donji dio adrese
              	
              	mov           (callgatex TMP_DLTA5).word ptr 0, ax

; gornji dio adrese
             	
              	shr           eax, 16
              	mov           (callgatex+6 TMP_DLTA5).word ptr 0, ax
              	
              	sgdt          (l_gdt_limit).fword ptr 0
             	
              	movzx         ebx, (l_gdt_limit).word ptr 0           ; 3FFh
              	mov           eax, l_gdt_base-2                       ; 8003F000h
              	
              	call		GetPhysicalAddress
              	mov		fizicka_adresa, eax

; izracunaj fizicku adresu GDT-a, vidi crazylordov papir za vise detalja..
               
                     mov		eax, l_gdt_base-2
              	xor		edx, edx
              	push		eax
              	mov           ecx, sys_info
              	add           ecx, SYS_BASIC_INFO_AllocationGranularity             ; 10000
              	mov           ecx, [ecx]
              	div           ecx
              	mov		edi, edx
              	pop		eax
              	inc		ebx
              	mov		esi, ebx				                     ; mapirana velicina
              	add		esi, edi
              	sub		eax, edi
              	call		GetPhysicalAddress
              	
              	mov		fizicka_adresa, eax
              	mov		mapped_size, esi
              	mov		ecx, mem_section

              	push          PAGE_READWRITE
              	push          0
              	push          1
              	lea           eax, mapped_size
              	push          eax
              	lea           eax, fizicka_adresa
              	push          eax
                   	push          esi
              	push          0
              	lea           eax, map_base
              	push          eax
              	push          -1
              	push          ecx
              	@sysenter     NtMapViewOfSection, 10

                     @get_delta    TMP_DLTA6, ecx

; sad patchajmo fizicku adresu GDT sa nasim zapisom, broj GDT_SELEKTOR
              	
              	add		map_base, edi
                     mov		eax, map_base
              	push		(callgatex TMP_DLTA6).dword ptr 0
              	pop		dword ptr [eax+GDT_SELEKTOR*8]
              	push		(callgatex+4 TMP_DLTA6).dword ptr 0
              	pop		dword ptr [eax+GDT_SELEKTOR*8+4]

; za farcall nam treba 6 bajtova, selektor:adresa. Sad patchajmo u varijabli 
; farcall nas selektor.
              	
              	mov		(farcall+4).word ptr 0, GDT_SELEKTOR*8+3

; E sad moramo uraditi jednu sitnicu koja je jako vazna, a to je  pri pozivu 
; ring0 koda page u kojem se on nalazi *mora* biti prisutan u trenutnom
; kontekstu, inace cemo dobiti jedan fini BSOD :) To radimo tako sto lockamo
; virtualnu memoriju koju zauzima kod koji se izvrsava u ring0 koristeci
; nativni API NtLockVirtualMemory().

	              lea           eax, ring0_proc TMP_DLTA6
	              push          eax
	              mov           eax, esp
	              
	              push          ring0_end - ring0_proc 
	              mov           edx, esp
              
              	push          MAP_PROCESS
	              push          edx                         ; lockamo kod i tako smanjujemo vjerojatnost da ce biti outpage-an
	              push          eax
                     push          -1                          ; trenutni proces
	              @sysenter     NtLockVirtualMemory, 4
	              add           esp, 8

; opali farcall i skoci na 08h:ring0_proc

	              call          fword ptr farcall

Pogledajmo sto se sve dogadja u ring0.

Prva stvar sto trebamo raditi jest spremiti *sve registre*

ring0_proc:          pushf
                     pusha
                     
; Moramo fs postaviti na 30h jer sistem ocekuje ds fs registar pokazuje na 
; Processor Control Region kad se nit izvodi u ring0, a selektor 30h pokazuje na 
; deskriptor cija je bazna adresa jednaka adresi PCR-a. Ovaj pocetni
; housekeeping jest nuzan uvijet ukoliko zelimo da nasa ring0 nit moze zvati 
; kernel-mode servise koje exporta hal i ntoskrnl.
                     
                     push          fs
                     mov           ebx, 30h
                     mov           fs, bx
                     
; Stvori nesto malo prostora na stogu, prolog standardni. Ovaj korak *moramo* 
; napraviti jer je vezan za logiku kojom radi page faulthandler. Inace cemo 
; dobiti BSOD.

                     sub           esp, 50h
                     mov           ebp, esp
                     
; postavi exception frame na NULL, spremi stari na stog

                     mov           edx, cs:[0FFDFF000h]
                     mov           ds:[0FFDFF000h], 0FFFFFFFFh
                     mov           [ebp], edx

; Spremi neka polja u TEB koja SoftIce zove "KSS EBP" (Kernel Stack Segment?); 
; ovo moramo uraditi te ovo polje postaviti na trap frame stvoren u prethodnom 
; koraku.
                     
                     mov           esi, cs:[0FFDFF124h]                      ; FFDFF124h = threnutni ETHREAD blok!
                     mov           edx, [esi+128h]                           ; KTRAP_FRAME *TrapFrame
                     mov           [ebp+4], edx                              ; spremi stari trap frame na stog
                     mov           [esi+128h], ebp                           ; i postavi novi

; spremi vrijeme kernela i thread mode (kernel/user)

                     mov           edi, [esi+137h]                           ;  uint32   KernelTime (138h???)
                     mov           [ebp+8], edi
                     
; postavi thread mode (kernel/user) baziran na selektoru koda

                     and           dword ptr [esi+137h], 1

; sad izracunavamo delta handle te u ring0 pozivamo rutinu za emuliranje 
; brainfuck progrma. Ukoliko se u njoj dogodi neka greska..hm..BSOD :)
                     
                     @get_delta    TMP_DLTA2, ecx

			mov           edi, (bf_sim_stdout_polje TMP_DLTA2).dword ptr 0

                     push          edi
                     push          (bf_sim_polje TMP_DLTA2).dword ptr 0
                     push          (bf_sim_clip TMP_DLTA2).dword ptr 0
                     call          brainfuck_simuliraj2

; sad je u edi = rezultat izvodjenja programa, tj. onaj "stdout". Izracunavamo 
; hash tog stringa:

			xor	       eax, eax				              ; inicijalizacija hasha

__1:			rol	       eax, 7
			xor	       al, [edi]
			inc	       edi					              ; slovo po slovo
			cmp	       byte ptr [edi], 0
			jnz	       __1

; sa PRODUKCIJA radimo provjeru da li je ovo testni ili stvarni kod za 
; deployment kod reversera. U slucaju da je PRODUKCIJA definirana, 
; radimo BSOD u slucaju da stdout ne sadrzi ono sto zelimo, inace se
; samo vracamo u ring3 kod.

IFDEF                PRODUKCIJA

; pogodite koji je pass za crackme? :)

                     gethash       <ph34rl355>                                      ; y0 dudez! =)
                     mov           ebx, hash
                     xor           ebx, eax
                     jz            ok_pass
                     
                     ;oh joy, we're being cracked! :o)
                     
                     call          get_ntoskrnl
                     mov           ebx, eax

                     gethash       <KeBugCheckEx>
                     push          hash
                     call          load_dll_address
                     call		eax, 0bAdc0Deh, 0Badh, 0c0deh, 0Badh, 0c0deh, 0Badh     ; bad code ;)                     
        
                     ; nisam bas bio puno mashtovit ;)
                     
ok_pass:             call          get_ntoskrnl
                     mov           ebx, eax

                     gethash       <KeBugCheckEx>
                     push          hash
                     call          load_dll_address
                     
                     ; "challenge completed!" = 63 68 61 6C 6C 65 6E 67 65 20 63 6F 6D 70 6C 65 74 65 64 21
                     
                     call		eax, 06368616Ch, 06C656E67h, 06520636Fh, 06D706C65h, 074656421h                 
ENDIF                     

; sad radimo standardni cleanup, vracamo sve kako je bilo i elegantno se vracamo..
                    
; vrati KSS EBP
                     mov           esi, cs:[0FFDFF124h]
                     mov           ebx, [ebp+4]
                     mov           [esi+128h], ebx
                     
; vrati exception frame

                     mov           ebx, [ebp]
                     mov           fs:[0], ebx
                                          
; vrati thread mode

                     mov           ebx, [ebp+8]
                     mov           esi, fs:[124h]
                     mov           byte ptr [esi+137h], bl
                     add           esp, 50h
                     pop           fs

                     popa
                     popf
                     retf

Onaj dio na pocetku i na kraju je jaaaako vazan ukoliko zelimo pozivate Ke* i 
ostale kerne-mode rutine, jerbo inace ce njihovi pozivi vracati samo BSOD. 
Koliko sam ja shvatio, mi jednostavno stvaramo ring0 nit "ni iz cega", te moramo
emulirati neka sranja na stogu koja radi PsCreateSystemThread(), prije nego bi
bili u stanju pozivati ntoskrnl exporte.

Nadalje, ja izgleda nikako nisam uspio natjerati da mi proradi jebeni kernel-
mode SEH: svaki put mi je top handler = 0ffffffffh :/ Mislim da se problem moze 
popraviti stvaranjem nove sistemske niti sa PsCreateSystemThread(), ili nekako 
drugacije, jer na netu postoji nekoliko kodova koji koriste SEH u ovakvom kodu
bez problema, ali pitam se da li su ih autori uopce testirali...

Nadalje, skok u ring0 ce sjebati apsolutno svaki application-level debugger i 
tracer, dumper i hooker, tako da je ovo savrsena zastita protiv njih. I syser i 
sice se na XP SP2 takodjer sjebu (daju BSOD) neposredno prije nekih dijelova 
koda, jerbo valjda nit radi nesto sto oni misle da ne bi smjela radit :)

Primjenjivost ovakve tehnike je univerzalna, kao zastita. Recimo unutar ring0 
mislim da je takodjer moguce napraviti running line (u slucaju da proradi SEH),
ili jos bolje, kao u dobrom starom MS-DOS-u, moguce je hookirat int 1 vektor i
preko njega raditi running line!!!

Mada bi mi trebalo valjda 5 dana i 5000 BSOD-ova samo da takvo nesto bolesno 
proradi, pa implementaciju prepustam citatelju.

Recimo kul forica jest pozovete KeDelayExecutionThread() u beskonacnoj petlji
sa nekim timoutom bezveznim, i time ste efektivno vas proces ucinili besmrtnim,
jerbo win ne mogu terminirati proces sve dok on ima ring0 komponentu aktivnu. 
Vrlo kul :)

U ring0 kodu trazenje kernel-mode exporta hal-a i ntoskrnl radimo na isti nacin
kao sto u ring3 radimo trazenje adresa API-ja: parsanjem export tablice. Samo 
sto za naci bazu ntoskrnl mozemo koristiti jedan trik:

 ---------------------------------------------------------------------------
; Trazi bazu od ntoskrnl.exe koristeci mid-delta metodu. Pocetna adresa jest
; adresa prvog zapisa u IDT koji se uvijek nalazi negdje unutar ntoskrnl.exe
; Mozemo koristiti sidt, ali posto se IDT referencira uvijek sa ptr-om na 
; adresi FFDFF038, mozemo ga koristiti direktno.
; i: r0
;
; o: eax = baza od ntoskrnl.exe. 
; NB: na XP SP2 ntoskrnl.exe se nalazi na adresi 804D7000
; ---------------------------------------------------------------------------
get_ntoskrnl:

IFDEF SYSER_DEBUGGING
                     mov           eax, 0804D7000h
                     retn
ENDIF        
                     
                     pusha
                     
                     mov           esi, ds:[0FFDFF038h]
                     lodsd                                            ; esi = ptr na IDT
                     cdq
                     lodsd                                            ; eax = ptr na neku adresu unutar ntoskrnl.exe
                     and           eax, -1 shl 12                     ; poravnaj na 1 page
                     add           eax, 1000h
                     
__loop:              sub           eax, 1000h
                     cmp           [eax], 00905a4dh                   ; provjeri 'MZ' signaturu                     
                     jnz           __loop
                     mov           [esp.Pushad_eax], eax
                     popa
                     
                     retn

Koji, kako sam ja sa veseljem punim desetina BSOD-ova otkrio, nece raditi 
ukoliko je syser aktivan jerbo on relocira IDT negdje u svoj vlastiti driver, 
tako da na kraju dobijemo njegovu adresu :) Zato sam i stavimo hard-coded adresu
da fja vraca ukoliko sa njim debuggiramo (0804D7000h na XP SP2).

Ali postoji jos jedna puno bolja metoda koju ja ovdje ne koristim, pogledajte 
[16] i [17] za vise informacija. PCR rox :>

Rekao sam takodjer da nas ring0 kod mora sam pocistiti stog. U slucaju da mu iz
ring3 proslijedjujemo parametre, moramo u opisu callgate dekskriptora naznaciti 
njihov broj, te u onaj zadnji retf u ring0 kodu dodati 4*broj_parametara. Cesto
se poneka informacija koju je lako prikupiti u ring3 moze (recimo baza od 
ntoskrnl) moze tako, na lak nacin, prenijeti ring0 kodu, bez da ovaj radi nesto
potencijalno bugovito.

Vratimo se sada na ring3 kod koji je opalio farcall. Radimo standardni cleanup:

; prvo unlockamo komadic virtualne memorije koju smo lockali
                     
	              @get_delta    TMP_DLTA7, ecx
	              lea           eax, ring0_proc TMP_DLTA7
	              push          eax
	              mov           eax, esp
	              
	              push          ring0_end - ring0_proc 
	              mov           edx, esp	              
                     
                     push          MAP_PROCESS
	              push          edx
	              push          eax
                     push          -1                          ; trenutni proces
	              @sysenter     NtUnlockVirtualMemory, 4
	              add           esp, 8

; nakon toga zatvaramo \Device\PhysicalMemory objekt te sve alocirane handlove 
; te clipboard i alociranu memoriju, te gasimo nas vlastiti proces. Sjetite se
; da se u onom C# kodu ova rutina pozva *zadnja*, tako da i u slucaju da u ovom
; kodu nesto se srusi, C# kod ce sam nakon 2s se ugasiti.

	              push          map_base
	              push          -1
	              @sysenter     NtUnmapViewOfSection, 2

	              push          mem_section
	              @sysenter     NtClose, 1
	              
	              @Ntfree       SYSTEM_MODULE_INFORMATION_SIZE, modinfo

	              @Ntfree       SYSTEM_INFO_SIZE, sys_info
                     
zatvori_clip:        @sysenter     NtUserCloseClipboard, 0
                     
                     jmp           kraj                     
                     
greska:              jmp           $

kraj:                push          ebx
                     push          -1
                     @sysenter     NtTerminateProcess, 2

I to bi bilo to :)

Ako ste pazljivo citali, a ne samo skocili na kraj da vidite koje je rjesenje 
crackmeja (lameri, sod off!), vec sad znate da je rjesnje crackmeja program 
napisan u brainfucku koji na stdout ispisuje "ph34rl355" pejstan na clipboard!

String "ph34rl355" se ne pojavljuje direktno u kodu, vec se u onoj ring0 
proceduri usporedjuju hashevi. Algoritam za hashiranje ima 7 instrukcija i moze 
se valjda brutforsati u nekom realnom vremenu :> Takodjer vjerojatno ima vec i 
gomila stringova (i ne samo stringova, nego opcenito nizova bajtova) koji imaju
isti hash, ali ja valjda pretpostavljam da ce brutforser biti dovoljno razuman 
samo da ukljuci one iz ASCII charseta i da pretpostavi da naziv ima nekakve veze
sa phearless :)

I to bi bilo to!

///////////////////////////////////////////////////////////////////////////
--[ Greets
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

       deroko        - kad cemo izmlatiti dum-duma? =)
       
       Shatterhand   - pazi da ti oci ne ispadnu dok ovo budes editirao :P
       
       DownBload     - uuu, sad smo profi :)) l33t!
       
       _bl00dz3r0_   - nmap -sS -sV -sU -vv -D picka,sisa,usta www.kurac.com :>
       
       De1Rekt0n     - nemoj vise DoS-ati forum :)
       
       reiser        - ide li taj crv vise, vec godinu dana ga pises? :D
       
       argv          - kad cemo vise pokoriti du? :-) Ja dam botove, ti wlan 
                       hackove :>
       
       ventura       - stvarno nista ne mozes nauciti kroz ovaj virmaking, ha?

te jos root9, VRKY, ap0x, Exoduks, BaCkSpAcE, esc, ACidCooKie, m4rk0, EArthquake 
i svima na #ugs, [es] (Asembler/Virii..) & blackhat forums.

vx 4ever.

///////////////////////////////////////////////////////////////////////////
--[ Reference
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\


 [1] http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Compression/RtlCompressBuffer.html

 [2] http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Compression/RtlDecompressBuffer.html

 [3] http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Compression/RtlGetCompressionWorkSpaceSize.html
 
 [4] www.pinvoke.net
 
 [5] http://www.elitesecurity.org/tema/106239-syser-vas-novi-omiljeni-kernel-mode-debugger
 
 [6] http://msdn.microsoft.com/net/sscli/
 
 [7] http://standards.ieee.org/regauth/oui/oui.txt

 [8] http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=6430F853-1120-48DB-8CC5-F2ABDC3ED314

 [9] http://www.microsoft.com/downloads/details.aspx?FamilyId=9B3A2CA6-3647-4070-9F41-A333C6B9181D&displaylang=en
 
 [10] www.fh-zwickau.de/doc/prmo/docs/pdf/24368901.pdf
 
 [11] ed2k://|file|windows_2000_source_code.zip|213748207|34BB9F3A3E8D3E0C4490A96EC30B9F3C|/

 [12] http://www.muppetlabs.com/~breadbox/bf/

 [13] http://www.madchat.org/vxdevl/vxmags/riot7/REALITY.004

 [14] http://www.codebreakers-journal.com/include/getdoc.php?id=28&article=21&mode=pdf
 
 [15] http://www.phrack.org/phrack/59/p59-0x10.txt
 
 [16] http://www.bitrake.com/phpBB2/viewtopic.php?t=121
 
 [17] http://www.rootkit.com/newsread.php?newsid=153
 
 [18] http://groups.google.com/groups?q=SEH+__try&hl=hr&lr=&group=comp.os.ms-windows.programmer.nt.kernel-mode&selm=5bunp0%24ok%40dim.intersurf.net&rnum=3


Korisna literatura:

       * Microsoft Press: Inside Microsoft Windows 2000 3d Edition by by David A. Solomon and Mark E. Russinovich
       
       * Microsoft Press: Programming the Microsoft Windows Driver Model 2nd Edition by Walter Oney
